From 6100464e04b03b75a2e82729b9b94f2ff2426904 Mon Sep 17 00:00:00 2001
From: John Harris <john.harris@nexusmods.com>
Date: Tue, 5 Jul 2022 12:30:31 +0100
Subject: [PATCH] feat: initial version

---
 README.md                                     |  2 +
 lib/nexus_semantic_logger.rb                  |  6 ++
 lib/nexus_semantic_logger/application.rb      | 65 +++++++++++++++++++
 .../datadog_formatter.rb                      | 38 +++++++++++
 nexus_semantic_logger.gemspec                 | 15 +++--
 5 files changed, 120 insertions(+), 6 deletions(-)
 create mode 100644 lib/nexus_semantic_logger.rb
 create mode 100644 lib/nexus_semantic_logger/application.rb
 create mode 100644 lib/nexus_semantic_logger/datadog_formatter.rb

diff --git a/README.md b/README.md
index 0ba0133..42c42f4 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
 # nexus_semantic_logger
+
+Configures a [semantic_logger](https://rubygems.org/gems/rails_semantic_logger) as required for NexusMods components.
diff --git a/lib/nexus_semantic_logger.rb b/lib/nexus_semantic_logger.rb
new file mode 100644
index 0000000..552dacb
--- /dev/null
+++ b/lib/nexus_semantic_logger.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+require 'nexus_semantic_logger/application'
+require 'nexus_semantic_logger/datadog_formatter'
+
+module NexusSemanticLogger
+end
diff --git a/lib/nexus_semantic_logger/application.rb b/lib/nexus_semantic_logger/application.rb
new file mode 100644
index 0000000..d810f2c
--- /dev/null
+++ b/lib/nexus_semantic_logger/application.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+require 'semantic_logger'
+
+module NexusSemanticLogger
+  class Application
+    include SemanticLogger::Loggable
+
+    def self.common(config)
+      # Set a safe logging level which individual environments can make more verbose if needed.
+      config.log_level = ENV.fetch('LOG_LEVEL', 'INFO')
+
+      # semanticlogger ddtrace correlation.
+      # From https://github.com/DataDog/dd-trace-rb/issues/1450
+      # Also https://docs.datadoghq.com/tracing/connect_logs_and_traces/ruby/
+      config.log_tags = {
+        request_id: :request_id,
+        dd: -> (_) {
+          correlation = Datadog.tracer.active_correlation
+          {
+            trace_id: correlation.trace_id.to_s,
+            span_id: correlation.span_id.to_s,
+            env: correlation.env.to_s,
+            service: correlation.service.to_s,
+            version: correlation.version.to_s
+          }
+        },
+        ddsource: ["ruby"]
+      }
+
+      # Synchronous mode is vital when puma is in single thread mode. Must add appender AFTER setting sync.
+      SemanticLogger.sync!
+
+      # Default logging is stdout in datadog compatible JSON.
+      config.rails_semantic_logger.format = NexusSemanticLogger::DatadogFormatter.new('users')
+      config.rails_semantic_logger.add_file_appender = false
+      config.semantic_logger.add_appender(io: $stdout, formatter: config.rails_semantic_logger.format)
+
+      logger.info('SemanticLogger initialised.', level: config.log_level)
+    end
+
+    def self.development(config)
+      # Enable debug globally.
+      config.log_level = ENV.fetch('LOG_LEVEL', 'DEBUG')
+
+      # Change default logging to coloured logging on stdout.
+      config.semantic_logger.clear_appenders!
+      config.semantic_logger.add_appender(io: $stdout, formatter: :color)
+      if ENV['DD_AGENT_HOST'].present? && ENV['DD_AGENT_PORT'].present?
+        # Development logs can be sent to datadog via a TCP logging endpoint on a local agent.
+        # Each port is assigned a particular service.
+        # See https://logger.rocketjob.io/appenders.html
+        config.semantic_logger.add_appender(appender: :tcp, server: "#{ENV['DD_AGENT_HOST']}:#{ENV['DD_AGENT_PORT']}",
+                                            formatter: config.rails_semantic_logger.format)
+      end
+
+      logger.info('SemanticLogger initialised in development.', level: config.log_level)
+    end
+
+    def self.test(config)
+      # Use human readable coloured output for logs when running tests.
+      config.semantic_logger.clear_appenders!
+      config.semantic_logger.add_appender(io: $stdout, formatter: :color)
+    end
+  end
+end
diff --git a/lib/nexus_semantic_logger/datadog_formatter.rb b/lib/nexus_semantic_logger/datadog_formatter.rb
new file mode 100644
index 0000000..6c28a5c
--- /dev/null
+++ b/lib/nexus_semantic_logger/datadog_formatter.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require 'semantic_logger'
+
+module NexusSemanticLogger
+  # Some attributes are reserved for use by Datadog.
+  #  https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#reserved-attributes
+  #   host     Supported by super.
+  #   source   Overridden by this class.
+  #   status   Supported by super as level, this class moves to status.
+  #   date     Supported by super as time, this class configures as date.
+  #   message  Supported by super.
+  #   env      Added by this class.
+  #   service  Added by this class.
+  class DatadogFormatter < SemanticLogger::Formatters::Raw
+    def initialize(service)
+      super(time_format: :iso_8601, time_key: :date)
+      @service = service
+    end
+
+    def call(log, logger)
+      hash = super(log, logger).clone
+      hash[:source] = :rails
+      level = hash.delete(:level)
+      hash[:status] = level
+      hash[:env] = Rails.env
+      hash[:service] = @service
+      hash.delete(:application)
+      hash.delete(:environment)
+      hash.delete('')
+      # ddtrace correlation inserted via log_tags, but datadog expects them in the root hash.
+      named_tags = hash.delete(:named_tags)
+      if named_tags.is_a?(Hash)
+        hash.deep_merge!(named_tags)
+      end
+      hash.to_json
+    end
+  end
+end
diff --git a/nexus_semantic_logger.gemspec b/nexus_semantic_logger.gemspec
index dc5832c..43f50f4 100644
--- a/nexus_semantic_logger.gemspec
+++ b/nexus_semantic_logger.gemspec
@@ -1,13 +1,16 @@
 Gem::Specification.new do |spec|
-  spec.name        = "nexus_semantic_logger"
-  spec.version     = "1.0.0"
-  spec.summary     = "semantic_logger usage for nexus"
-  spec.authors     = ["Johnathon Harris"]
-  spec.email       = "john.harris@nexusmods.com"
+  spec.name = "nexus_semantic_logger"
+  spec.version = "1.0.0"
+  spec.summary = "semantic_logger usage for nexus"
+  spec.authors = ["Johnathon Harris"]
+  spec.email = "john.harris@nexusmods.com"
   # Specify which files should be added to the gem when it is released.
   # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
   spec.files = Dir.chdir(File.expand_path(__dir__)) do
     %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
   end
+  spec.require_paths = ['lib']
+  spec.add_dependency('amazing_print')
+  spec.add_dependency('rails_semantic_logger')
+  spec.add_dependency('net_tcp_client') # For TCP logging.
 end
-
-- 
GitLab