Skip to content

Commit aff45ef

Browse files
authored
Merge pull request #4 from caspiano/feat/log-backend
Add `OpenTelemetry::Instrumentation::LogBackend`
2 parents f16ba34 + e874ed7 commit aff45ef

File tree

4 files changed

+91
-12
lines changed

4 files changed

+91
-12
lines changed

spec/log_backend_spec.cr

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require "./spec_helper"
2+
require "../src/opentelemetry-instrumentation/log_backend"
3+
4+
describe OpenTelemetry::Instrumentation::LogBackend do
5+
it "should add the log to the existing span" do
6+
memory = IO::Memory.new
7+
OpenTelemetry.configure do |config|
8+
config.service_name = "Crystal OTel Instrumentation - OpenTelemetry::Instrumentation::LogBackend"
9+
config.service_version = "1.0.0"
10+
config.exporter = OpenTelemetry::Exporter.new(variant: :io, io: memory)
11+
end
12+
13+
random_source = Random::DEFAULT.base64
14+
::Log.setup(sources: random_source, level: Log::Severity::Trace, backend: OpenTelemetry::Instrumentation::LogBackend.new)
15+
16+
trace = OpenTelemetry.trace
17+
trace.in_span("Fake Span") do |_span|
18+
exception = Exception.new("Oh no!")
19+
::Log.with_context do
20+
::Log.context.set(context: 42)
21+
::Log.for(random_source).warn(exception: exception, &.emit("Oh no!", data: "stuff"))
22+
end
23+
end
24+
25+
memory.rewind
26+
strings = memory.gets_to_end
27+
json_finder = FindJson.new(strings)
28+
29+
traces = [] of JSON::Any
30+
while json = json_finder.pull_json
31+
traces << JSON.parse(json)
32+
end
33+
34+
traces[0]["spans"][0]["kind"].should eq 1
35+
traces[0]["spans"][0]["name"].should eq "Fake Span"
36+
traces[0]["spans"][0]["events"][0]["attributes"]["data"].should eq "stuff"
37+
traces[0]["spans"][0]["events"][0]["attributes"]["context"].should eq 42
38+
end
39+
end

src/opentelemetry-instrumentation.cr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require "opentelemetry-api"
22
require "tracer"
33
require "./ext/*"
4+
require "./opentelemetry-instrumentation/log_backend"
45

56
macro finished
67
require "./opentelemetry/instrumentation/**"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
require "opentelemetry-api"
2+
require "log"
3+
require "log/backend"
4+
5+
# # OpenTelemetry::Instrumentation::LogBackend
6+
#
7+
# Provides a simple implementation of `Log::Backend`.
8+
#
9+
# If a `Log` call takes place within a span, the log and its context are added as an event.
10+
#
11+
# ```
12+
# require "opentelemetry-instrumentation/log_backend"
13+
# Log.builder.bind("*", Log::Severity::Warn, OpenTelemetry::Instrumentation::LogBackend.new)
14+
# ```
15+
class OpenTelemetry::Instrumentation::LogBackend < ::Log::Backend
16+
def self.apply_log_entry(entry, event)
17+
entry.context.each do |key, value|
18+
if (raw = value.raw).is_a?(ValueTypes)
19+
event[key.to_s] = raw
20+
end
21+
end
22+
23+
entry.data.each do |key, value|
24+
if (raw = value.raw).is_a?(ValueTypes)
25+
event[key.to_s] = raw
26+
end
27+
end
28+
29+
event["message"] = String.build { |io| ::Log::ShortFormat.format(entry, io) }
30+
if exception = entry.exception
31+
event["exception"] = exception.to_s
32+
if backtrace = exception.backtrace?.try(&.join('\n'))
33+
event["backtrace"] = backtrace
34+
end
35+
end
36+
event["source"] = entry.source if !entry.source.empty?
37+
event["timestamp"] = entry.timestamp.to_s
38+
end
39+
40+
def write(entry : ::Log::Entry)
41+
if (span = OpenTelemetry::Trace.current_span)
42+
span.add_event("Log.#{entry.severity.label}#{" - #{entry.source}" unless entry.source.empty?}") do |event|
43+
self.class.apply_log_entry(entry, event)
44+
end
45+
end
46+
end
47+
end

src/opentelemetry/instrumentation/crystal/log.cr

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require "../instrument"
2-
require "io/memory"
2+
require "../../../opentelemetry-instrumentation/log_backend"
33

44
# # OpenTelemetry::Instrumentation::CrystalLog
55
#
@@ -57,23 +57,15 @@ unless_enabled?("OTEL_CRYSTAL_DISABLE_INSTRUMENTATION_LOG") do
5757
dsl.emit(result.to_s)
5858
end
5959
backend = @backend
60-
io = IO::Memory.new
61-
ShortFormat.format(entry, io)
62-
span.add_event("Log.#{entry.severity.label}#{!entry.source.empty? ? " - #{entry.source}" : ""}") do |event|
63-
event["message"] = io.rewind.gets_to_end
64-
if exception = entry.exception
65-
event["exception"] = exception.to_s
66-
event["backtrace"] = exception.backtrace.join("\n")
67-
end
68-
event["source"] = entry.source if !entry.source.empty?
69-
event["timestamp"] = entry.timestamp.to_s
60+
span.add_event("Log.#{entry.severity.label}#{" - #{entry.source}" unless entry.source.empty?}") do |event|
61+
OpenTelemetry::Instrumentation::LogBackend.apply_log_entry(entry, event)
7062
end
7163

7264
return unless backend
7365

7466
backend.dispatch entry
7567
else
76-
previous_def {|e| yield e}
68+
previous_def { |e| yield e }
7769
end
7870
end
7971
{% end %}

0 commit comments

Comments
 (0)