Skip to content
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

[WIP] Bridge: Logging Bridge for SwiftLog #535

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
21 changes: 14 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ let package = Package(
.library(name: "InMemoryExporter", type: .static, targets: ["InMemoryExporter"]),
.library(name: "DatadogExporter", type: .static, targets: ["DatadogExporter"]),
.library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]),
.library(name: "OTelSwiftLog" type: .static, targets: ["OTelSwiftLog"])
.executable(name: "simpleExporter", targets: ["SimpleExporter"]),
.executable(name: "OTLPExporter", targets: ["OTLPExporter"]),
.executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]),
Expand All @@ -48,6 +49,10 @@ let package = Package(
dependencies: []),
.target(name: "OpenTelemetrySdk",
dependencies: ["OpenTelemetryApi"]),
.target(name: "OTelSwiftLog",
dependencies: ["OpenTelemetrySdk",
.product(name: "Logging", package: "swift-log")],
path: "Sources/Bridges/OTelSwiftLog"),
.target(name: "ResourceExtension",
dependencies: ["OpenTelemetrySdk"],
path: "Sources/Instrumentation/SDKResourceExtension",
Expand Down Expand Up @@ -116,15 +121,17 @@ let package = Package(
.target(name: "PersistenceExporter",
dependencies: ["OpenTelemetrySdk"],
path: "Sources/Exporters/Persistence"),
.testTarget(name: "OTelSwiftLogTests",
dependencies: ["OTelSwiftLog"],
path: "Tests/BridgesTests/OTelSwiftLog"),
.testTarget(name: "NetworkStatusTests",
dependencies: ["NetworkStatus", .product(name: "Reachability", package: "Reachability.swift")],
path: "Tests/InstrumentationTests/NetworkStatusTests"),
.testTarget(name: "OpenTelemetryApiTests",
dependencies: ["OpenTelemetryApi"],
path: "Tests/OpenTelemetryApiTests"),
.testTarget(name: "OpenTelemetrySdkTests",
dependencies: ["OpenTelemetryApi",
"OpenTelemetrySdk"],
dependencies: ["OpenTelemetrySdk"],
path: "Tests/OpenTelemetrySdkTests"),
.testTarget(name: "ResourceExtensionTests",
dependencies: ["ResourceExtension", "OpenTelemetrySdk"],
Expand Down Expand Up @@ -173,27 +180,27 @@ let package = Package(
dependencies: ["OpenTelemetryApi"],
path: "Examples/Logging Tracer"),
.target(name: "SimpleExporter",
dependencies: ["OpenTelemetrySdk", "JaegerExporter", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
dependencies: ["JaegerExporter", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
path: "Examples/Simple Exporter",
exclude: ["README.md"]),
.target(name: "OTLPExporter",
dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporterGrpc", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
dependencies: ["OpenTelemetryProtocolExporterGrpc", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
path: "Examples/OTLP Exporter",
exclude: ["README.md"]),
.target(name: "OTLPHTTPExporter",
dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
dependencies: ["OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
path: "Examples/OTLP HTTP Exporter",
exclude: ["README.md"]),
.target(name: "PrometheusSample",
dependencies: ["OpenTelemetrySdk", "PrometheusExporter"],
dependencies: ["PrometheusExporter"],
path: "Examples/Prometheus Sample",
exclude: ["README.md"]),
.target(name: "DatadogSample",
dependencies: ["DatadogExporter"],
path: "Examples/Datadog Sample",
exclude: ["README.md"]),
.target(name: "StableMetricSample",
dependencies: ["OpenTelemetrySdk", "OpenTelemetryApi", "OpenTelemetryProtocolExporter", .product(name: "GRPC", package: "grpc-swift")],
dependencies: ["OpenTelemetryProtocolExporter", .product(name: "GRPC", package: "grpc-swift")],
path: "Examples/Stable Metric Sample",
exclude: ["README.md"]),
.target(name: "NetworkSample",
Expand Down
14 changes: 11 additions & 3 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ let package = Package(
.library(name: "InMemoryExporter", type: .static, targets: ["InMemoryExporter"]),
.library(name: "DatadogExporter", type: .static, targets: ["DatadogExporter"]),
.library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]),
.library(name: "OTelSwiftLog", type: .static, targets: ["OTelSwiftLog"]),
.executable(name: "simpleExporter", targets: ["SimpleExporter"]),
.executable(name: "OTLPExporter", targets: ["OTLPExporter"]),
.executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]),
Expand All @@ -53,6 +54,10 @@ let package = Package(
dependencies: ["OpenTelemetrySdk"],
path: "Sources/Instrumentation/SDKResourceExtension",
exclude: ["README.md"]),
.target(name: "OTelSwiftLog",
dependencies: ["OpenTelemetrySdk",
.product(name: "Logging", package: "swift-log")],
path: "Sources/Bridges/OTelSwiftLog"),
.target(name: "URLSessionInstrumentation",
dependencies: ["OpenTelemetrySdk", "NetworkStatus"],
path: "Sources/Instrumentation/URLSession",
Expand All @@ -70,8 +75,8 @@ let package = Package(
exclude: ["README.md"]),
.target(name: "OpenTracingShim",
dependencies: [
"OpenTelemetrySdk",
.product(name: "Opentracing", package: "opentracing-objc")
"OpenTelemetrySdk",
.product(name: "Opentracing", package: "opentracing-objc")
],
path: "Sources/Importers/OpenTracingShim",
exclude: ["README.md"]),
Expand Down Expand Up @@ -121,6 +126,9 @@ let package = Package(
.target(name: "PersistenceExporter",
dependencies: ["OpenTelemetrySdk"],
path: "Sources/Exporters/Persistence"),
.testTarget(name: "OTelSwiftLogTests",
dependencies: ["OTelSwiftLog"],
path: "Tests/BridgesTests/OTelSwiftLog"),
.testTarget(name: "NetworkStatusTests",
dependencies: [
"NetworkStatus",
Expand All @@ -144,7 +152,7 @@ let package = Package(
path: "Tests/InstrumentationTests/URLSessionTests"),
.testTarget(name: "OpenTracingShimTests",
dependencies: ["OpenTracingShim",
"OpenTelemetrySdk"],
"OpenTelemetrySdk"],
path: "Tests/ImportersTests/OpenTracingShim"),
.testTarget(name: "SwiftMetricsShimTests",
dependencies: ["SwiftMetricsShim",
Expand Down
15 changes: 11 additions & 4 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ let package = Package(
.library(name: "InMemoryExporter", type: .static, targets: ["InMemoryExporter"]),
.library(name: "DatadogExporter", type: .static, targets: ["DatadogExporter"]),
.library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]),
.library(name: "OTelSwiftLog", type: .static, targets: ["OTelSwiftLog"]),
.executable(name: "simpleExporter", targets: ["SimpleExporter"]),
.executable(name: "OTLPExporter", targets: ["OTLPExporter"]),
.executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]),
Expand All @@ -48,6 +49,10 @@ let package = Package(
dependencies: []),
.target(name: "OpenTelemetrySdk",
dependencies: ["OpenTelemetryApi"]),
.target(name: "OTelSwiftLog",
dependencies: ["OpenTelemetrySdk",
.product(name: "Logging", package: "swift-log")],
path: "Sources/Bridges/OTelSwiftLog"),
.target(name: "ResourceExtension",
dependencies: ["OpenTelemetrySdk"],
path: "Sources/Instrumentation/SDKResourceExtension",
Expand All @@ -68,8 +73,8 @@ let package = Package(
exclude: ["README.md"]),
.target(name: "OpenTracingShim",
dependencies: [
"OpenTelemetrySdk",
.product(name: "Opentracing", package: "opentracing-objc")
"OpenTelemetrySdk",
.product(name: "Opentracing", package: "opentracing-objc")
],
path: "Sources/Importers/OpenTracingShim",
exclude: ["README.md"]),
Expand Down Expand Up @@ -119,6 +124,9 @@ let package = Package(
.target(name: "PersistenceExporter",
dependencies: ["OpenTelemetrySdk"],
path: "Sources/Exporters/Persistence"),
.testTarget(name: "OTelSwiftLogTests",
dependencies: ["OTelSwiftLog"],
path: "Tests/BridgesTests/OTelSwiftLog"),
.testTarget(name: "NetworkStatusTests",
dependencies: [
"NetworkStatus",
Expand All @@ -128,8 +136,7 @@ let package = Package(
dependencies: ["OpenTelemetryApi"],
path: "Tests/OpenTelemetryApiTests"),
.testTarget(name: "OpenTelemetrySdkTests",
dependencies: ["OpenTelemetryApi",
"OpenTelemetrySdk"],
dependencies: ["OpenTelemetrySdk"],
path: "Tests/OpenTelemetrySdkTests"),
.testTarget(name: "ResourceExtensionTests",
dependencies: ["ResourceExtension", "OpenTelemetrySdk"],
Expand Down
151 changes: 151 additions & 0 deletions Sources/Bridges/OTelSwiftLog/LogHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk
import Logging

// let the bridgename be the url of the package?
let bridgeName: String = "OTelSwiftLog"
let version: String = "1.0.0"

/// A custom log handler to translate swift logs into otel logs
struct OTelLogHandler: LogHandler {

/// Get or set the configured log level.
///
/// - note: `LogHandler`s must treat the log level as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`. It is acceptable to provide some form of global log level override
/// that means a change in log level on a particular `LogHandler` might not be reflected in any
/// `LogHandler`.
public var logLevel: Logging.Logger.Level = .info

/// loggerProvider to use for the bridge.
private var loggerProvider : LoggerProvider
private var logger: OpenTelemetryApi.Logger

// Define metadata for this handler
public var metadata: Logging.Logger.Metadata = [:]
public subscript(metadataKey key: String) -> Logging.Logger.Metadata.Value? {
get {
return self.metadata[key]
}
set {
self.metadata[key] = newValue
}
}

/// create a new OtelLogHandler
/// - Parameter loggerProvider: The logger provider to use in the bridge. Defaults to the global logger provider.
/// - Parameter includeTraceContext : boolean flag used for the logger builder
/// - Parameter attributes: attributes to apply to the logger builder
public init(loggerProvider: LoggerProvider = OpenTelemetrySdk.LoggerProviderSdk(),
includeTraceContext : Bool = true,
attributes: [String:AttributeValue] = [String:AttributeValue]()) {

self.loggerProvider = loggerProvider
self.logger = self.loggerProvider.loggerBuilder(instrumentationScopeName: bridgeName)
.setInstrumentationVersion(version)
.setEventDomain("device")
.setIncludeTraceContext(true)
.setAttributes(attributes)
.setIncludeTraceContext(includeTraceContext)
.build()
}

public func log(level: Logging.Logger.Level,
message: Logging.Logger.Message,
metadata: Logging.Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {


// This converts log atrributes to otel attributes
var otelattributes: [String: AttributeValue] = [
"source": AttributeValue.string(source),
"file": AttributeValue.string(file),
"function": AttributeValue.string(function),
"line": AttributeValue.int(Int(line)),
]

// Convert metadata from the method parameter to AttributeValue and assign it to otelattributes
if let metadata = metadata {
let methodMetadata = convertMetadata(metadata)
otelattributes.merge(methodMetadata) { _, new in new }
}

// Convert metadata from the struct property to AttributeValue and merge it with otelattributes
let structMetadata = convertMetadata(self.metadata)
otelattributes.merge(structMetadata) { _, new in new }

// Build the log record and emit it
let event = self.logger.logRecordBuilder().setSeverity(convertSeverity(level: level))
.setBody(AttributeValue.string(message.description))
.setAttributes(otelattributes)

if let context = OpenTelemetry.instance.contextProvider.activeSpan?.context {
_ = event.setSpanContext(context)
}
event.emit()

}



}

func convertMetadata(_ metadata: Logging.Logger.Metadata) -> [String: AttributeValue] {
var convertedAttributes: [String: AttributeValue] = [:]

// Iterate over each key-value pair in the metadata dictionary
for (key, value) in metadata {
// Convert each value to AttributeValue
let attributeValue = convertToAttributeValue(value)

// Store the converted value with its corresponding key in the attributes dictionary
convertedAttributes[key] = attributeValue
}

return convertedAttributes
}

// Function to recursively convert nested dictionaries to AttributeValue
func convertToAttributeValue(_ value: Logging.Logger.Metadata.Value) -> AttributeValue {
switch value {
case .dictionary(let nestedDictionary):
// If value is a nested dictionary, recursively convert it
var nestedAttributes: [String: AttributeValue] = [:]
for (nestedKey, nestedValue) in nestedDictionary {
nestedAttributes[nestedKey] = convertToAttributeValue(nestedValue)
}
return AttributeValue.set(AttributeSet(labels: nestedAttributes))
case .array(let nestedArray):
// If value is a nested array, recursively convert it
let nestedValues = nestedArray.map { convertToAttributeValue($0) }
return AttributeValue.array(AttributeArray(values: nestedValues))
case .string(let str):
return AttributeValue(str)
case .stringConvertible(let strConvertable):
return AttributeValue(strConvertable.description)

}
}

func convertSeverity(level: Logging.Logger.Level) -> OpenTelemetryApi.Severity{
switch level {
case .trace:
return OpenTelemetryApi.Severity.trace
case .debug:
return OpenTelemetryApi.Severity.debug
case .info:
return OpenTelemetryApi.Severity.info
case .notice:
return OpenTelemetryApi.Severity.info2
case .warning:
return OpenTelemetryApi.Severity.warn
case .error:
return OpenTelemetryApi.Severity.error
case .critical:
return OpenTelemetryApi.Severity.error2 //should this be fatal instead?
}
}
70 changes: 70 additions & 0 deletions Sources/Bridges/OTelSwiftLog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
### How to use OTelLogHandler

1. Using Default Scope:

```swift
import Foundation
import Logging

// Initialize the OTelLogHandler without a custom scope (using default scope)
let otelLogHandler = OTelLogHandler()

// Create a Logger instance with the default log handler
var logger = Logger(label: "com.example.myapp")
logger.logLevel = .debug
logger.handler = otelLogHandler

// Log messages with various log levels
logger.debug("This is a debug message")

// Log with additional metadata
logger[metadataKey: "customKey"] = "customValue"
logger.info("Logging with additional metadata")
```

2. Using Custom Scope:
```swift
import Foundation
import Logging

// Initialize a custom instrumentation scope
let customScope = InstrumentationScope(
name: "MyCustomInstrumentationScope",
version: "1.0.0",
eventDomain: "MyEventDomain",
schemaUrl: "https://example.com/schema",
includeTraceContext: true,
attributes: ["customAttribute": AttributeValue.string("customValue")]
)

// Initialize the OTelLogHandler with custom scope
let otelLogHandler = OTelLogHandler(scope: customScope)

// Create a Logger instance with the custom log handler
var logger = Logger(label: "com.example.myapp")
logger.logLevel = .debug
logger.handler = otelLogHandler

// Log messages with various log levels
logger.debug("This is a debug message")
```

3. Using Custom Logger Provider:
```swift
import Foundation
import Logging

// Initialize a custom LoggerProvider
let customLoggerProvider = MyCustomLoggerProvider()

// Initialize the OTelLogHandler with custom LoggerProvider and default scope
let otelLogHandler = OTelLogHandler(loggerProvider: customLoggerProvider)

// Create a Logger instance with the custom log handler
var logger = Logger(label: "com.example.myapp")
logger.logLevel = .debug
logger.handler = otelLogHandler

// Log messages with various log levels
logger.debug("This is a debug message")
```
Loading
Loading