Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,23 @@ class SendingCrashReportTests: XCTestCase {
let lastRUMAttributes = try XCTUnwrap(crashContext.lastRUMAttributes?.contextInfo)
DDAssertJSONEqual(contextAttributes, lastRUMAttributes.merging(crashReportAttributes) { $1 })
}

func testWhenGlobalLogAttributeIsAdded_itUpdatesCrashContext() throws {
// Given
Logs.enable(in: core)
CrashReporting.enable(with: CrashReporterMock(), in: core)
core.flush()

// When
let attributeKey: String = .mockRandom()
let attributeValue: String = .mockRandom()
Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core)
core.flush()

// Then (the log attribute reaches the crash context)
let feature = try XCTUnwrap(core.get(feature: CrashReportingFeature.self))
let crashContext = try XCTUnwrap(feature.crashContextProvider.currentCrashContext)
let logAttributes = try XCTUnwrap(crashContext.lastLogAttributes)
XCTAssertEqual(logAttributes.attributes[attributeKey] as? String, attributeValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class TracingWithLoggingIntegrationTests: XCTestCase {

override func setUp() {
super.setUp()
core = PassthroughCoreMock(messageReceiver: LogMessageReceiver.mockAny())
core = PassthroughCoreMock()
core.messageBus.subscribe(receiver: LogMessageReceiver.mockAny())
}

override func tearDown() {
Expand Down
20 changes: 8 additions & 12 deletions DatadogCore/Tests/Datadog/TracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -618,10 +618,9 @@ class TracerTests: XCTestCase {
// MARK: - Integration With Logging Feature

func testSendingSpanLogs() throws {
let logging: LogsFeature = .mockWith(
messageReceiver: LogMessageReceiver.mockAny()
)
let logging: LogsFeature = .mockWith()
try core.register(feature: logging)
core.messageBus.subscribe(receiver: logging.logMessageReceiver)

Trace.enable(with: config, in: core)
let tracer = Tracer.shared(in: core)
Expand Down Expand Up @@ -651,10 +650,9 @@ class TracerTests: XCTestCase {
}

func testSendingSpanLogsWithErrorFromArguments() throws {
let logging: LogsFeature = .mockWith(
messageReceiver: LogMessageReceiver.mockAny()
)
let logging: LogsFeature = .mockWith()
try core.register(feature: logging)
core.messageBus.subscribe(receiver: logging.logMessageReceiver)

Trace.enable(with: config, in: core)
let tracer = Tracer.shared(in: core)
Expand All @@ -676,10 +674,9 @@ class TracerTests: XCTestCase {
}

func testSendingSpanLogsWithErrorFromNSError() throws {
let logging: LogsFeature = .mockWith(
messageReceiver: LogMessageReceiver.mockAny()
)
let logging: LogsFeature = .mockWith()
try core.register(feature: logging)
core.messageBus.subscribe(receiver: logging.logMessageReceiver)

Trace.enable(with: config, in: core)
let tracer = Tracer.shared(in: core)
Expand Down Expand Up @@ -707,10 +704,9 @@ class TracerTests: XCTestCase {
}

func testSendingSpanLogsWithErrorFromSwiftError() throws {
let logging: LogsFeature = .mockWith(
messageReceiver: LogMessageReceiver.mockAny()
)
let logging: LogsFeature = .mockWith()
try core.register(feature: logging)
core.messageBus.subscribe(receiver: logging.logMessageReceiver)

Trace.enable(with: config, in: core)
let tracer = Tracer.shared(in: core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ class TracingURLSessionHandlerTests: XCTestCase {
override func setUp() {
super.setUp()
let receiver = ContextMessageReceiver(samplerProvider: SamplerProvider(sampleRate: .mockAny()))
core = PassthroughCoreMock(messageReceiver: CombinedFeatureMessageReceiver([
LogMessageReceiver.mockAny(),
receiver
]))
core = PassthroughCoreMock(messageReceiver: receiver)
core.messageBus.subscribe(receiver: LogMessageReceiver.mockAny())

tracer = .mockWith(
core: core,
Expand Down
9 changes: 5 additions & 4 deletions DatadogLogs/Sources/Feature/LogsFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ internal struct LogsFeature: DatadogRemoteFeature {

let messageReceiver: FeatureMessageReceiver

/// Typed-bus receiver for `LogMessage`.
let logMessageReceiver: LogMessageReceiver

let logEventMapper: LogEventMapper?

let backtraceReporter: BacktraceReporting?
Expand All @@ -40,10 +43,7 @@ internal struct LogsFeature: DatadogRemoteFeature {
customIntakeURL: customIntakeURL,
telemetry: telemetry
),
messageReceiver: CombinedFeatureMessageReceiver(
LogMessageReceiver(logEventMapper: logEventMapper),
WebViewLogReceiver()
),
messageReceiver: WebViewLogReceiver(),
dateProvider: dateProvider,
backtraceReporter: backtraceReporter
)
Expand All @@ -59,6 +59,7 @@ internal struct LogsFeature: DatadogRemoteFeature {
self.logEventMapper = logEventMapper
self.requestBuilder = requestBuilder
self.messageReceiver = messageReceiver
self.logMessageReceiver = LogMessageReceiver(logEventMapper: logEventMapper)
self.dateProvider = dateProvider
self.backtraceReporter = backtraceReporter
self.attributes = SynchronizedAttributes(attributes: [:])
Expand Down
24 changes: 6 additions & 18 deletions DatadogLogs/Sources/Feature/MessageReceivers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,21 @@ import Foundation
import DatadogInternal

/// Receiver to consume a Log message
internal struct LogMessageReceiver: FeatureMessageReceiver {
internal final class LogMessageReceiver: BusMessageReceiver {
/// The log event mapper
let logEventMapper: LogEventMapper?

/// Process messages receives from the bus.
///
/// - Parameters:
/// - message: The Feature message
/// - core: The core from which the message is transmitted.
func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool {
guard case let .payload(log as LogMessage) = message else {
return false
}
init(logEventMapper: LogEventMapper?) {
self.logEventMapper = logEventMapper
}

func receive(message log: LogMessage, from core: DatadogCoreProtocol) {
core.scope(for: LogsFeature.self).eventWriteContext { context, writer in
let builder = LogEventBuilder(
service: log.service ?? context.service,
loggerName: log.logger,
networkInfoEnabled: log.networkInfoEnabled ?? false,
eventMapper: logEventMapper
eventMapper: self.logEventMapper
Comment thread
maxep marked this conversation as resolved.
)

builder.createLogEvent(
Expand Down Expand Up @@ -56,18 +51,11 @@ internal struct LogMessageReceiver: FeatureMessageReceiver {
callback: writer.write
)
}

return true
}
}

/// Receiver to consume a Log event coming from Browser SDK.
internal struct WebViewLogReceiver: FeatureMessageReceiver {
/// Process messages receives from the bus.
///
/// - Parameters:
/// - message: The Feature message
/// - core: The core from which the message is transmitted.
func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool {
guard case var .webview(.log(event)) = message else {
return false
Expand Down
7 changes: 7 additions & 0 deletions DatadogLogs/Sources/Logs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public enum Logs {
)

try core.register(feature: feature)

// Subscribe typed-bus receivers:
core.messageBus.subscribe(receiver: feature.logMessageReceiver)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid accumulating log receivers on re-enable

When Logs.enable(in:) is called more than once for the same core, each call creates a new LogMessageReceiver instance and subscribes it here. The typed bus de-duplicates only the same object identity, whereas the old legacy receiver was replaced under the single "logging" feature key, so subsequent TracingWithLoggingIntegration / span.log LogMessages are delivered to every accumulated receiver and written multiple times.

Useful? React with 👍 / 👎.

}

/// Adds a custom attribute to all future logs sent by any logger created from the provided Core.
Expand Down Expand Up @@ -120,6 +123,10 @@ public enum Logs {
}

private static func sendAttributesChanged(for feature: LogsFeature, in core: DatadogCoreProtocol) {
// Keep log-attribute updates on the legacy bus until CrashReporting migrates:
// `CrashContextCoreProvider` still consumes `LogEventAttributes` as a legacy
// `FeatureMessageReceiver`, so routing this through the typed bus would drop the
// global log attributes from crash reports.
core.send(
message: .payload(LogEventAttributes(
attributes: feature.attributes.getAttributes()
Expand Down
100 changes: 44 additions & 56 deletions DatadogLogs/Tests/LogMessageReceiverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,23 @@ class LogMessageReceiverTests: XCTestCase {
func testReceivePartialLogMessage() throws {
// Given
let expectation = expectation(description: "Send log")
let core = PassthroughCoreMock(
context: .mockWith(service: "service-test"),
messageReceiver: LogMessageReceiver.mockAny()
)
let core = PassthroughCoreMock(context: .mockWith(service: "service-test"))
core.messageBus.subscribe(receiver: LogMessageReceiver.mockAny())
core.onEventWriteContext = { _ in expectation.fulfill() }

// When
core.send(
message: .payload(
LogMessage(
logger: "logger-test",
service: nil,
date: .mockDecember15th2019At10AMUTC(),
message: "message-test",
error: nil,
level: .info,
thread: "thread-test",
networkInfoEnabled: nil,
userAttributes: nil,
internalAttributes: nil
)
core.messageBus.send(
message: LogMessage(
logger: "logger-test",
service: nil,
date: .mockDecember15th2019At10AMUTC(),
message: "message-test",
error: nil,
level: .info,
thread: "thread-test",
networkInfoEnabled: nil,
userAttributes: nil,
internalAttributes: nil
)
)

Expand All @@ -56,27 +52,23 @@ class LogMessageReceiverTests: XCTestCase {
func testReceiveCompleteLogMessage() throws {
// Given
let expectation = expectation(description: "Send log")
let core = PassthroughCoreMock(
context: .mockAny(),
messageReceiver: LogMessageReceiver.mockAny()
)
let core = PassthroughCoreMock(context: .mockAny())
core.messageBus.subscribe(receiver: LogMessageReceiver.mockAny())
core.onEventWriteContext = { _ in expectation.fulfill() }

// When
core.send(
message: .payload(
LogMessage(
logger: "logger-test",
service: "service-test",
date: .mockDecember15th2019At10AMUTC(),
message: "message-test",
error: .mockAny(),
level: .info,
thread: "thread-test",
networkInfoEnabled: true,
userAttributes: ["user": "attribute"],
internalAttributes: ["internal": "attribute"]
)
core.messageBus.send(
message: LogMessage(
logger: "logger-test",
service: "service-test",
date: .mockDecember15th2019At10AMUTC(),
message: "message-test",
error: .mockAny(),
level: .info,
thread: "thread-test",
networkInfoEnabled: true,
userAttributes: ["user": "attribute"],
internalAttributes: ["internal": "attribute"]
)
)

Expand Down Expand Up @@ -105,29 +97,25 @@ class LogMessageReceiverTests: XCTestCase {
func testReceiveRejectedLogMessage() throws {
// Given
let expectation = expectation(description: "Open scope but don't send log")
let core = PassthroughCoreMock(
context: .mockWith(service: "service-test"),
messageReceiver: LogMessageReceiver(
logEventMapper: SyncLogEventMapper { _ in nil }
)
)
let core = PassthroughCoreMock(context: .mockWith(service: "service-test"))
core.messageBus.subscribe(receiver: LogMessageReceiver(
logEventMapper: SyncLogEventMapper { _ in nil }
))
core.onEventWriteContext = { _ in expectation.fulfill() }

// When
core.send(
message: .payload(
LogMessage(
logger: "logger-test",
service: nil,
date: .mockDecember15th2019At10AMUTC(),
message: "message-test",
error: nil,
level: .info,
thread: "thread-test",
networkInfoEnabled: nil,
userAttributes: nil,
internalAttributes: nil
)
core.messageBus.send(
message: LogMessage(
logger: "logger-test",
service: nil,
date: .mockDecember15th2019At10AMUTC(),
message: "message-test",
error: nil,
level: .info,
thread: "thread-test",
networkInfoEnabled: nil,
userAttributes: nil,
internalAttributes: nil
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,21 @@ internal struct TracingWithLoggingIntegration {
)
: nil

core.send(
message: .payload(
LogMessage(
logger: "trace",
service: service,
date: date,
message: message,
error: extractedError,
level: level,
thread: Thread.current.dd.name,
networkInfoEnabled: networkInfoEnabled,
userAttributes: userAttributes,
internalAttributes: [
Constants.traceIDKey: String(spanContext.traceID, representation: .hexadecimal),
Constants.spanIDKey: String(spanContext.spanID, representation: .hexadecimal)
]
)
core.messageBus.send(
message: LogMessage(
logger: "trace",
service: service,
date: date,
message: message,
error: extractedError,
level: level,
thread: Thread.current.dd.name,
networkInfoEnabled: networkInfoEnabled,
userAttributes: userAttributes,
internalAttributes: [
Constants.traceIDKey: String(spanContext.traceID, representation: .hexadecimal),
Constants.spanIDKey: String(spanContext.spanID, representation: .hexadecimal)
]
),
else: fallback
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,14 @@ extension LogsFeature {
}

extension LogMessageReceiver: AnyMockable {
public static func mockAny() -> Self {
.mockWith()
public static func mockAny() -> LogMessageReceiver {
mockWith()
}

public static func mockWith(
logEventMapper: LogEventMapper? = nil
) -> Self {
.init(
logEventMapper: logEventMapper
)
) -> LogMessageReceiver {
LogMessageReceiver(logEventMapper: logEventMapper)
}
}

Expand Down