Skip to content
Open
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
.default(enabledTraits: ["ConfigReader"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.11.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.12.0"),
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.1.1"),
.package(url: "https://github.com/apple/swift-configuration.git", from: "1.0.0", traits: ["CommandLineArguments"]),
],
Expand Down
1 change: 1 addition & 0 deletions Sources/ConsoleLogger/ConsoleLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public struct ConsoleLogger<T: LoggerFragment>: LogHandler, Sendable {
var record = LogRecord(
level: event.level,
message: event.message,
error: event.error,
metadata: event.metadata,
source: event.source,
file: event.file,
Expand Down
2 changes: 1 addition & 1 deletion Sources/ConsoleLogger/Docs.docc/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You then pass the resulting fragment to the ``ConsoleLogger``, which is a `Swift

`ConsoleLogger` includes a couple of default ``LoggerFragment``s that can be used out of the box:

- ``LoggerFragment/default``, which outputs the label of the logger, the level of the logged message, the logged message itself, the metadata and the file location of the logged message, including the line.
- ``LoggerFragment/default``, which outputs the label of the logger, the level of the logged message, the logged message itself, the error and metadata, and the file location of the logged message, including the line.
- ``LoggerFragment/timestampDefault(timestampSource:)``, which adds a timestamp at the front of the default fragment.

### Creating a LoggerFragment
Expand Down
2 changes: 1 addition & 1 deletion Sources/ConsoleLogger/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ A `SwiftLog` `LogHandler` implementation for customizable logging to a console.

- ``LogRecord``
- ``ConsoleLoggerPrinter``
- ``DefaultConsoleLoggerPrinter``
- ``DefaultConsoleLoggerPrinter``
4 changes: 4 additions & 0 deletions Sources/ConsoleLogger/LogRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public struct LogRecord {
public init(
level: Logger.Level,
message: Logger.Message,
error: (any Error)?,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm assuming breaking changes are ok, since it's a beta release?

metadata: Logger.Metadata? = nil,
source: String,
file: String,
Expand All @@ -17,6 +18,7 @@ public struct LogRecord {
) {
self.level = level
self.message = message
self.error = error
self.metadata = metadata
self.source = source
self.file = file
Expand All @@ -32,6 +34,8 @@ public struct LogRecord {
public var level: Logger.Level
/// The logged message
public var message: Logger.Message
/// The error associated with the logged message
public var error: (any Error)?
/// The metadata explicitly associated with the logged message
public var metadata: Logger.Metadata?
/// The source of the log message, usually the module name
Expand Down
10 changes: 7 additions & 3 deletions Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,18 +259,22 @@ public struct MessageFragment: LoggerFragment {
}
}

/// Writes the combined metadata to the output, and requests a separator for the next fragment only if the metadata was not empty.
/// Writes the combined metadata (including the logged error) to the output, and requests a separator for the next fragment only if the metadata was not empty.
///
/// This fragment is considered to not have content if the metadata is empty.
public struct MetadataFragment: LoggerFragment {
public init() {}

public func hasContent(record: inout LogRecord) -> Bool {
!record.allMetadata().isEmpty
!record.allMetadata().isEmpty || record.error != nil
}

public func write(_ record: inout LogRecord, to output: inout FragmentOutput) {
let allMetadata = record.allMetadata()
var allMetadata = record.allMetadata()
if let error = record.error {
allMetadata["error.message"] = "\(error)"
allMetadata["error.type"] = "\(String(reflecting: type(of: error)))"
}
Comment on lines +273 to +277

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Would you prefer to instead have this be part of allMetadata()?


guard !allMetadata.isEmpty else { return }

Expand Down
29 changes: 29 additions & 0 deletions Tests/ConsoleLoggerTests/LoggerFragmentBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ struct LoggerFragmentBuilderTests {
#expect(printer.testOutputQueue.first == "ConsoleLogger [ codes.vapor.console ] [ INFO ] Test message")
}

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
@Test("Error Metadata Fragment")
func errorMetadataFragment() throws {
let printer = TestingConsoleLoggerPrinter()

@LoggerFragmentBuilder<0>
var fragment: some LoggerFragment {
MessageFragment()
MetadataFragment()
}

let logger = Logger(label: "codes.vapor.console") { label in
ConsoleLogger(fragment: fragment, printer: printer, label: label)
}

logger.info("Test message", error: TestError())

#expect(
printer.testOutputQueue.first
== "Test message[error.message: Boom!, error.type: ConsoleLoggerTests.LoggerFragmentBuilderTests.TestError]"
)
}

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
@Test("Conditional Fragment", arguments: [true, false])
func conditionalFragment(includeTimestamp: Bool) throws {
Expand Down Expand Up @@ -209,4 +232,10 @@ struct LoggerFragmentBuilderTests {
// so the next `.separated(" ")` still inserts a space.
#expect(printer.testOutputQueue.first == "Hello[ INFO ] Test message")
}

struct TestError: Error, CustomStringConvertible {
var description: String {
"Boom!"
}
}
}
21 changes: 21 additions & 0 deletions Tests/ConsoleLoggerTests/LoggingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ struct ConsoleLoggerTests {
expect(printer: printer, logs: .critical, message: "critical")
}

@Test("Error")
func error() {
let printer = TestingConsoleLoggerPrinter()
let logger = Logger(label: "codes.vapor.console") { label in
ConsoleLogger(printer: printer, label: label, level: .info)
}

logger.warning("warning", error: TestError(), metadata: ["error.message": "Overridden", "error.type": "Overridden"])
expect(
printer: printer,
logs: .warning,
message: "warning [error.message: Boom!, error.type: ConsoleLoggerTests.ConsoleLoggerTests.TestError]"
)
}

@Test("Metadata")
func metadata() {
let printer = TestingConsoleLoggerPrinter()
Expand Down Expand Up @@ -216,6 +231,12 @@ struct ConsoleLoggerTests {
expect(printer: printer, logs: level, message: expectedMessage)
}
#endif

struct TestError: Error, CustomStringConvertible {
var description: String {
"Boom!"
}
}
}

private func expect(
Expand Down