-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setup rails for JSON logging #1568
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
require "json" | ||
require 'openc3/utilities/logger' | ||
require 'openc3/utilities/authorization' | ||
|
||
# Use with Rails Semantic Logger | ||
module OpenC3 | ||
class CosmosRailsFormatter | ||
include OpenC3::Authorization | ||
|
||
def call(log, _appender = nil) | ||
#<SemanticLogger::Log:0x0000ffffa5002b20 @level=:info, @thread_name="puma srv tp 001", @name="MicroservicesController", | ||
# @time=2024-09-22 18:04:27.955490052 +0000, @tags=[], @named_tags={}, @level_index=2, @message="Completed #traefik", | ||
# @payload={:controller=>"MicroservicesController", :action=>"traefik", :format=>"HTML", :method=>"GET", | ||
# :path=>"/openc3-api/traefik", :status=>200, :view_runtime=>0.41, :allocations=>1438, :status_message=>"OK", | ||
# :exception_object=>#<RuntimeError: death>}, @named_tags={:request_id=>"65446ef0-734e-4488-aa5f-e85b7b573fd8"}, | ||
# @duration=3.2528750002384186, @metric=nil, @metric_amount=nil, @dimensions=nil, @exception=nil, @backtrace=nil> | ||
message = log.message | ||
other = {} | ||
other[:thread_name] = log.thread_name | ||
other[:duration] = log.duration if log.duration | ||
named_tags = log.named_tags | ||
username = nil | ||
if named_tags | ||
other[:request_id] = named_tags[:request_id] | ||
if named_tags[:token] | ||
begin | ||
user = user_info(named_tags[:token]) | ||
rescue | ||
user = {} | ||
end | ||
username = user['username'] | ||
# Open Source username (EE has the actual username) | ||
username ||= 'anonymous' | ||
end | ||
end | ||
username ||= 'anonymous' | ||
payload = log.payload | ||
if payload | ||
other[:path] = payload[:path] | ||
other[:status] = payload[:status] | ||
other[:controller] = payload[:controller] | ||
other[:action] = payload[:action] | ||
other[:format] = payload[:format] | ||
other[:method] = payload[:method] | ||
other[:allocations] = payload[:allocations] | ||
other[:view_runtime] = payload[:view_runtime] | ||
if payload[:exception_object] | ||
other[:exception_message] = payload[:exception_object].message | ||
other[:exception_class] = payload[:exception_object].class.to_s | ||
other[:exception_backtrace] = payload[:exception_object].backtrace.as_json | ||
end | ||
end | ||
# This happens for a separate exception log entry which we want to not include the backtrace a second time | ||
if log.exception | ||
message = "Exception was raised - #{log.exception.class}:#{log.exception.message}" unless message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't log.message always set? So this will never be set? Also do we want the exception message to override the passed message? Seems like an exception is more important ... I'm not sure how log.exception even gets set though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not always. I found for some exception logs they just include the exception object, not a message. |
||
end | ||
return OpenC3::Logger.build_log_data(log.level.to_s.upcase, message, user: username, type: OpenC3::Logger::LOG, url: nil, other: other).as_json(:allow_nan => true).to_json(:allow_nan => true) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,53 +97,53 @@ def self.microservice_name=(name) | |
# below the method name log level. | ||
# @param block [Proc] Block to call which should return a string to append | ||
# to the log message | ||
def debug(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
log_message(DEBUG_LEVEL, message, scope: scope, user: user, type: type, url: url, &block) if @level <= DEBUG | ||
def debug(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
log_message(DEBUG_LEVEL, message, scope: scope, user: user, type: type, url: url, other: other, &block) if @level <= DEBUG | ||
end | ||
|
||
# (see #debug) | ||
def info(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
log_message(INFO_LEVEL, message, scope: scope, user: user, type: type, url: url, &block) if @level <= INFO | ||
def info(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
log_message(INFO_LEVEL, message, scope: scope, user: user, type: type, url: url, other: other, &block) if @level <= INFO | ||
end | ||
|
||
# (see #debug) | ||
def warn(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
log_message(WARN_LEVEL, message, scope: scope, user: user, type: type, url: url, &block) if @level <= WARN | ||
def warn(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
log_message(WARN_LEVEL, message, scope: scope, user: user, type: type, url: url, other: other, &block) if @level <= WARN | ||
end | ||
|
||
# (see #debug) | ||
def error(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
log_message(ERROR_LEVEL, message, scope: scope, user: user, type: type, url: url, &block) if @level <= ERROR | ||
def error(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
log_message(ERROR_LEVEL, message, scope: scope, user: user, type: type, url: url, other: other, &block) if @level <= ERROR | ||
end | ||
|
||
# (see #debug) | ||
def fatal(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
log_message(FATAL_LEVEL, message, scope: scope, user: user, type: type, url: url, &block) if @level <= FATAL | ||
def fatal(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
log_message(FATAL_LEVEL, message, scope: scope, user: user, type: type, url: url, other: other, &block) if @level <= FATAL | ||
end | ||
|
||
# (see #debug) | ||
def self.debug(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
self.instance.debug(message, scope: scope, user: user, type: type, url: url, &block) | ||
def self.debug(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
self.instance.debug(message, scope: scope, user: user, type: type, url: url, other: other, &block) | ||
end | ||
|
||
# (see #debug) | ||
def self.info(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
self.instance.info(message, scope: scope, user: user, type: type, url: url, &block) | ||
def self.info(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
self.instance.info(message, scope: scope, user: user, type: type, url: url, other: other, &block) | ||
end | ||
|
||
# (see #debug) | ||
def self.warn(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
self.instance.warn(message, scope: scope, user: user, type: type, url: url, &block) | ||
def self.warn(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
self.instance.warn(message, scope: scope, user: user, type: type, url: url, other: other, &block) | ||
end | ||
|
||
# (see #debug) | ||
def self.error(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
self.instance.error(message, scope: scope, user: user, type: type, url: url, &block) | ||
def self.error(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
self.instance.error(message, scope: scope, user: user, type: type, url: url, other: other, &block) | ||
end | ||
|
||
# (see #debug) | ||
def self.fatal(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, &block) | ||
self.instance.fatal(message, scope: scope, user: user, type: type, url: url, &block) | ||
def self.fatal(message = nil, scope: @@scope, user: nil, type: LOG, url: nil, other: nil, &block) | ||
self.instance.fatal(message, scope: scope, user: user, type: type, url: url, other: other, &block) | ||
end | ||
|
||
# @return [Logger] The logger instance | ||
|
@@ -172,23 +172,33 @@ def scope=(scope) | |
@@scope = scope | ||
end | ||
|
||
def build_log_data(log_level, message, user: nil, type: nil, url: nil, other: nil, &block) | ||
time = Time.now.utc | ||
# timestamp iso8601 with 6 decimal places to match the python output format | ||
data = { time: time.to_nsec_from_epoch, '@timestamp' => time.iso8601(6), level: log_level } | ||
data[:microservice_name] = @microservice_name if @microservice_name | ||
data[:detail] = @detail_string if @detail_string | ||
data[:user] = user if user # EE: If a user is passed, put its name. Don't include user data if no user was passed. | ||
if block_given? | ||
message = yield | ||
end | ||
data[:container_name] = @container_name | ||
data[:message] = message if message | ||
data[:type] = type if type | ||
data[:url] = url if url | ||
data = data.merge(other) if other | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to make sure we don't overlap keys in other but I think we control the complete other hash so it should be ok. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We control other, so are capable of not reusing any names. |
||
return data | ||
end | ||
|
||
def self.build_log_data(log_level, message, user: nil, type: nil, url: nil, other: nil) | ||
self.instance.build_log_data(log_level, message, user: user, type: type, url: url, other: other) | ||
end | ||
|
||
protected | ||
|
||
def log_message(log_level, message, scope:, user:, type:, url:) | ||
def log_message(log_level, message, scope:, user:, type:, url:, other: nil, &block) | ||
@@mutex.synchronize do | ||
time = Time.now.utc | ||
# timestamp iso8601 with 6 decimal places to match the python output format | ||
data = { time: time.to_nsec_from_epoch, '@timestamp' => time.iso8601(6), level: log_level } | ||
data[:microservice_name] = @microservice_name if @microservice_name | ||
data[:detail] = @detail_string if @detail_string | ||
data[:user] = user if user # EE: If a user is passed, put its name. Don't include user data if no user was passed. | ||
if block_given? | ||
message = yield | ||
end | ||
data[:container_name] = @container_name | ||
data[:message] = message | ||
data[:type] = type | ||
data[:url] = url if url | ||
data = build_log_data(log_level, message, user: user, type: type, url: url, other: other, &block) | ||
if @stdout | ||
case log_level | ||
when WARN_LEVEL, ERROR_LEVEL, FATAL_LEVEL | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does the payload get set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comes from the semantic logging gem changing Rails.