Skip to content

Commit

Permalink
Merge pull request #131 from amzn/metadata-provider
Browse files Browse the repository at this point in the history
Add metadata provider.
  • Loading branch information
tachyonics committed Nov 12, 2023
2 parents bc32fa9 + a1edfdd commit d4ea018
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 10 deletions.
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
run-codeql-linux:
name: Run CodeQL on Linux
runs-on: ubuntu-latest
container: swift:5.8
permissions:
security-events: write

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ jobs:
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-20.04]
swift: ["5.8"]
swift: ["5.9"]
runs-on: ${{ matrix.os }}
steps:
- uses: swift-actions/setup-swift@v1.23.0
- uses: swift-actions/setup-swift@v1.25.0
with:
swift-version: ${{ matrix.swift }}
- uses: actions/checkout@v2
Expand All @@ -28,10 +28,10 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04]
swift: ["5.7.3", "5.6.3"]
swift: ["5.8.1", "5.7.3"]
runs-on: ${{ matrix.os }}
steps:
- uses: swift-actions/setup-swift@v1.23.0
- uses: swift-actions/setup-swift@v1.25.0
with:
swift-version: ${{ matrix.swift }}
- uses: actions/checkout@v2
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,25 @@ struct MyPerInvocationContextInitializer: StandardJSONSmokeServerPerInvocationCo
This will enable tracing for any operation handlers that use Swift Concurrency (async/await). You will also
need to setup an Instrumentation backend by following the instructions [here](https://swiftpackageindex.com/apple/swift-distributed-tracing/1.0.0/documentation/tracing/traceyourapplication).

# Logging

The Smoke Framework provides a Metadata Provider that can be used to decorate any logs emitted from the structured concurrency tree
rooted at the operation handlers. What this means is that metadata such as the `internalRequestId` and `incomingOperation` will be
added to logs emitted from libraries called from operation handlers even if an explicit logger instance isn't passed into the library
function.

```swift
import Logging
import SmokeOperations

...

let metadataProvider = Logging.MetadataProvider.smokeframework
let factory = <provided by your logging backend>

LoggingSystem.bootstrap(factory, metadataProvider: metadataProvider)
```

# Further Concepts

## The Application Context
Expand Down
50 changes: 50 additions & 0 deletions Sources/SmokeOperations/MetadataProvider+smokeFramework.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// MetadataProvider+smokeFramework
// SmokeOperations
//

import Logging
import ServiceContextModule

extension Logger.MetadataProvider {
/// A metadata provider exposing the attributes of the current invocation.
///
/// - Parameters:
/// - internalRequestIdKey: The metadata key of the internalRequestId. Defaults to "internalRequestId".
/// - incomingOperationKey: The metadata key of the incomingOperation. Defaults to "incomingOperation".
/// - externalRequestIdKey: The metadata key of the externalRequestId. Defaults to "externalRequestId".
/// - Returns: A metadata provider ready to use with Logging.
public static func smokeFramework(internalRequestIdKey: String = "internalRequestId",
incomingOperationKey: String = "incomingOperation",
externalRequestIdKey: String = "externalRequestId") -> Logger.MetadataProvider {
.init {
guard let invocationContext = ServiceContext.current?.invocationContext else { return [:] }

var metadataProvider: Logger.Metadata = [
internalRequestIdKey: "\(invocationContext.internalRequestId)",
incomingOperationKey: "\(invocationContext.incomingOperation)",
]

if let externalRequestId = invocationContext.externalRequestId {
metadataProvider[externalRequestIdKey] = "\(externalRequestId)"
}

return metadataProvider
}
}

/// A metadata provider exposing the attributes of the current invocation with the default key names.
public static let smokeFramework = Logger.MetadataProvider.smokeFramework()
}
4 changes: 3 additions & 1 deletion Sources/SmokeOperations/OperationTraceContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import Tracing

public struct RequestSpanParameters {
public let operationName: String
public let internalRequestId: String

public init(operationName: String) {
public init(operationName: String, internalRequestId: String) {
self.operationName = operationName
self.internalRequestId = internalRequestId
}
}

Expand Down
53 changes: 53 additions & 0 deletions Sources/SmokeOperations/ServiceContext+invocationContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// ServiceContext+invocationContext
// SmokeOperations
//

import ServiceContextModule

public struct InvocationContext {
var internalRequestId: String
var incomingOperation: String
var externalRequestId: String?

public init(internalRequestId: String, incomingOperation: String, externalRequestId: String?) {
self.internalRequestId = internalRequestId
self.incomingOperation = incomingOperation
self.externalRequestId = externalRequestId
}
}

extension ServiceContext {
public var invocationContext: InvocationContext? {
get {
self[InvocationContextKey.self]
}
set {
self[InvocationContextKey.self] = newValue
}
}
}

extension InvocationContext: CustomStringConvertible {
public var description: String {
return internalRequestId
}
}

private enum InvocationContextKey: ServiceContextKey {
typealias Value = InvocationContext

static var nameOverride: String? = "smoke-framework-invocation-context"
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ extension SmokeInvocationTraceContext: OperationTraceContext {
var serviceContext = ServiceContext.current ?? .topLevel
let operationName = parameters.operationName
InstrumentationSystem.instrument.extract(requestHead.headers, into: &serviceContext, using: HTTPHeadersExtractor())

let invocationContext = InvocationContext(internalRequestId: parameters.internalRequestId,
incomingOperation: parameters.operationName,
externalRequestId: self.externalRequestId)
serviceContext.invocationContext = invocationContext

let parentSpan = InstrumentationSystem.tracer.startSpan("ServerRequest", context: serviceContext, ofKind: .server)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public struct StandardHTTP1OperationRequestHandler<SelectorType>: HTTP1Operation
query: query,
pathShape: .null)

let tracingOptions = getTracingOptions(for: "InvalidOperation")
let tracingOptions = getTracingOptions(for: "InvalidOperation", internalRequestId: internalRequestId)
let actions = actionsProvider(tracingOptions)
let requestLogger = actions.requestStartTraceAction?() ?? originalLogger

Expand All @@ -241,7 +241,7 @@ public struct StandardHTTP1OperationRequestHandler<SelectorType>: HTTP1Operation
invocationContext: invocationContext)
return
} catch {
let tracingOptions = self.getTracingOptions(for: "FailedHandlerSelection")
let tracingOptions = self.getTracingOptions(for: "FailedHandlerSelection", internalRequestId: internalRequestId)
let actions = actionsProvider(tracingOptions)
let requestLogger = actions.requestStartTraceAction?() ?? originalLogger

Expand All @@ -266,7 +266,8 @@ public struct StandardHTTP1OperationRequestHandler<SelectorType>: HTTP1Operation
query: query,
pathShape: shape)

let tracingOptions = self.getTracingOptions(for: handler.operationIdentifer.description)
let tracingOptions = self.getTracingOptions(for: handler.operationIdentifer.description,
internalRequestId: internalRequestId)
let actions = actionsProvider(tracingOptions)
let requestLogger = actions.requestStartTraceAction?() ?? originalLogger

Expand All @@ -277,11 +278,11 @@ public struct StandardHTTP1OperationRequestHandler<SelectorType>: HTTP1Operation
invocationReportingProvider: actions.invocationReportingProvider)
}

private func getTracingOptions(for operationName: String)
private func getTracingOptions(for operationName: String, internalRequestId: String)
-> OperationTraceContextOptions {
let createRequestSpan: CreateRequestSpan
if self.enableTracingWithSwiftConcurrency {
let parameters = RequestSpanParameters(operationName: operationName)
let parameters = RequestSpanParameters(operationName: operationName, internalRequestId: internalRequestId)
createRequestSpan = .ifRequired(parameters)
} else {
createRequestSpan = .never
Expand Down

0 comments on commit d4ea018

Please sign in to comment.