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

Add metadata provider. #131

Merged
merged 2 commits into from
Nov 12, 2023
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
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