Skip to content

Commit

Permalink
Merge pull request #1143 from DataDog/jward/RUMM-2950-log-attribute-f…
Browse files Browse the repository at this point in the history
…orwarding

RUMM-2950 Fix log attribute forwarding to RUM
  • Loading branch information
fuzzybinary authored Jan 27, 2023
2 parents aca47a5 + 09ce218 commit 42b5014
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 4 deletions.
3 changes: 2 additions & 1 deletion Sources/Datadog/Logging/RemoteLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ internal final class RemoteLogger: LoggerProtocol {
baggage: [
"type": log.error?.kind,
"stack": log.error?.stack,
"source": "logger"
"source": "logger",
"attributes": userAttributes
]
)
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ internal struct RUMAddCurrentViewErrorCommand: RUMCommand {
self.stack = stack

self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes)
self.isCrash = self.attributes.removeValue(forKey: CrossPlatformAttributes.errorIsCrash) as? Bool
self.isCrash = self.attributes.removeValue(forKey: CrossPlatformAttributes.errorIsCrash)?.decoded()
}

init(
Expand Down
18 changes: 17 additions & 1 deletion Sources/Datadog/RUMMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,29 @@ internal typealias RUMErrorSourceType = RUMErrorEvent.Error.SourceType

internal extension RUMErrorSourceType {
static func extract(from attributes: inout [AttributeKey: AttributeValue]) -> Self {
return (attributes.removeValue(forKey: CrossPlatformAttributes.errorSourceType) as? String)
return (attributes.removeValue(forKey: CrossPlatformAttributes.errorSourceType))
.flatMap({
$0.decoded()
})
.flatMap {
return RUMErrorEvent.Error.SourceType(rawValue: $0)
} ?? .ios
}
}

internal extension AttributeValue {
func decoded<T>() -> T? {
switch self {
case let codable as DDAnyCodable:
return codable.value as? T
case let val as T:
return val
default:
return nil
}
}
}

/// Describes the type of a RUM Action.
public enum RUMUserActionType {
case tap
Expand Down
141 changes: 141 additions & 0 deletions Tests/DatadogTests/Datadog/LoggerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,147 @@ class LoggerTests: XCTestCase {
}
}

func testWhenSendingErrorOrCriticalLogsWithAttributes_itCreatesRUMErrorForCurrentViewWithAttributes() throws {
let logging: LoggingFeature = .mockAny()
core.register(feature: logging)

let rum: RUMFeature = .mockWith(messageReceiver: ErrorMessageReceiver())
core.register(feature: rum)

// given
let logger = Logger.builder.build(in: core)
Global.rum = RUMMonitor(
core: core,
dependencies: RUMScopeDependencies(
core: core,
rumFeature: rum
).replacing(viewUpdatesThrottlerFactory: { NoOpRUMViewUpdatesThrottler() }),
dateProvider: SystemDateProvider()
)
Global.rum.startView(viewController: mockView)
defer { Global.rum = DDNoopRUMMonitor() }

// when
let attributeValueA: String = .mockRandom()
logger.error("error message", attributes: [
"any_attribute_a": attributeValueA
])
let attributeValueB: String = .mockRandom()
logger.critical("critical message", attributes: [
"any_attribute_b": attributeValueB
])

// then
let rumEventMatchers = try core.waitAndReturnRUMEventMatchers()
let rumErrorMatcher1 = rumEventMatchers.first { $0.model(isTypeOf: RUMErrorEvent.self) }
let rumErrorMatcher2 = rumEventMatchers.last { $0.model(isTypeOf: RUMErrorEvent.self) }
try XCTUnwrap(rumErrorMatcher1).model(ofType: RUMErrorEvent.self) { rumModel in
XCTAssertEqual(rumModel.error.message, "error message")
XCTAssertEqual(rumModel.error.source, .logger)
XCTAssertNil(rumModel.error.stack)
let attributeValue = (rumModel.context?.contextInfo["any_attribute_a"] as? DDAnyCodable)?.value as? String
XCTAssertEqual(attributeValue, attributeValueA)
}
try XCTUnwrap(rumErrorMatcher2).model(ofType: RUMErrorEvent.self) { rumModel in
XCTAssertEqual(rumModel.error.message, "critical message")
XCTAssertEqual(rumModel.error.source, .logger)
XCTAssertNil(rumModel.error.stack)
let attributeValue = (rumModel.context?.contextInfo["any_attribute_b"] as? DDAnyCodable)?.value as? String
XCTAssertEqual(attributeValue, attributeValueB)
}
}

func testWhenSendingErrorOrCriticalLogs_itCreatesRUMErrorWithProperSourceType() throws {
let logging: LoggingFeature = .mockAny()
core.register(feature: logging)

let rum: RUMFeature = .mockWith(messageReceiver: ErrorMessageReceiver())
core.register(feature: rum)

// given
let logger = Logger.builder.build(in: core)
Global.rum = RUMMonitor(
core: core,
dependencies: RUMScopeDependencies(
core: core,
rumFeature: rum
).replacing(viewUpdatesThrottlerFactory: { NoOpRUMViewUpdatesThrottler() }),
dateProvider: SystemDateProvider()
)
Global.rum.startView(viewController: mockView)
defer { Global.rum = DDNoopRUMMonitor() }

// when
logger.error("error message", attributes: [
"_dd.error.source_type": "flutter"
])
logger.critical("critical message", attributes: [
"_dd.error.source_type": "react-native"
])

// then
let rumEventMatchers = try core.waitAndReturnRUMEventMatchers()
let rumErrorMatcher1 = rumEventMatchers.first { $0.model(isTypeOf: RUMErrorEvent.self) }
let rumErrorMatcher2 = rumEventMatchers.last { $0.model(isTypeOf: RUMErrorEvent.self) }
try XCTUnwrap(rumErrorMatcher1).model(ofType: RUMErrorEvent.self) { rumModel in
XCTAssertEqual(rumModel.error.message, "error message")
XCTAssertEqual(rumModel.error.source, .logger)
XCTAssertNil(rumModel.error.stack)
XCTAssertEqual(rumModel.error.sourceType, .flutter)
}
try XCTUnwrap(rumErrorMatcher2).model(ofType: RUMErrorEvent.self) { rumModel in
XCTAssertEqual(rumModel.error.message, "critical message")
XCTAssertEqual(rumModel.error.source, .logger)
XCTAssertNil(rumModel.error.stack)
XCTAssertEqual(rumModel.error.sourceType, .reactNative)
}
}

func testWhenSendingErrorOrCriticalLogs_itCreatesRUMErrorWithProperIsCrash() throws {
let logging: LoggingFeature = .mockAny()
core.register(feature: logging)

let rum: RUMFeature = .mockWith(messageReceiver: ErrorMessageReceiver())
core.register(feature: rum)

// given
let logger = Logger.builder.build(in: core)
Global.rum = RUMMonitor(
core: core,
dependencies: RUMScopeDependencies(
core: core,
rumFeature: rum
).replacing(viewUpdatesThrottlerFactory: { NoOpRUMViewUpdatesThrottler() }),
dateProvider: SystemDateProvider()
)
Global.rum.startView(viewController: mockView)
defer { Global.rum = DDNoopRUMMonitor() }

// when
logger.error("error message", attributes: [
"_dd.error.is_crash": false
])
logger.critical("critical message", attributes: [
"_dd.error.is_crash": true
])

// then
let errorEvents = core.waitAndReturnEvents(of: RUMFeature.self, ofType: RUMErrorEvent.self)
let error1 = try XCTUnwrap(errorEvents.first)
XCTAssertEqual(error1.error.message, "error message")
XCTAssertEqual(error1.error.source, .logger)
XCTAssertNil(error1.error.stack)
// swiftlint:disable:next xct_specific_matcher
XCTAssertEqual(error1.error.isCrash, false)

let error2 = try XCTUnwrap(errorEvents.last)
XCTAssertEqual(error2.error.message, "critical message")
XCTAssertEqual(error2.error.source, .logger)
XCTAssertNil(error2.error.stack)
// swiftlint:disable:next xct_specific_matcher
XCTAssertEqual(error2.error.isCrash, true)
}

// MARK: - Integration With Active Span

func testGivenBundlingWithTraceEnabledAndTracerRegistered_whenSendingLog_itContainsActiveSpanAttributes() throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,17 @@ class ErrorMessageReceiverTests: XCTestCase {
defer { Global.rum = DDNoopRUMMonitor() }

// When
let mockAttribute: String = .mockRandom()
core.send(
message: .error(
message: "message-test",
baggage: [
"type": "type-test",
"stack": "stack-test",
"source": "logger"
"source": "logger",
"attributes": [
"any-key": mockAttribute
]
]
)
)
Expand All @@ -94,5 +98,7 @@ class ErrorMessageReceiverTests: XCTestCase {
XCTAssertEqual(event.error.type, "type-test")
XCTAssertEqual(event.error.stack, "stack-test")
XCTAssertEqual(event.error.source, .logger)
let attributeValue = (event.context?.contextInfo["any-key"] as? DDAnyCodable)?.value as? String
XCTAssertEqual(attributeValue, mockAttribute)
}
}

0 comments on commit 42b5014

Please sign in to comment.