Skip to content

Commit 2b18fc5

Browse files
authored
Use StderrLogHandler for rich logging features. (apple#1066)
- Facilitates apple#507, apple#642. - Plumb logger with logger option access into every command. - StderrLogHandler allows logging to be enhanced for ANSI color mode for log levels, JSONL log output, replacing `--debug` with `--level level`. - Global logger is now just an Application.swift fileprivate only used for initial args processing. - Log with timestamp at debug and trace level. - Metadata output is crude at present; we can refine this in a follow-up.
1 parent a5607a8 commit 2b18fc5

65 files changed

Lines changed: 246 additions & 143 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Sources/ContainerCommands/Application.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@ import Foundation
2525
import Logging
2626
import TerminalProgress
2727

28+
// This logger is only used until `asyncCommand.run()`.
2829
// `log` is updated only once in the `validate()` method.
29-
nonisolated(unsafe) var log = {
30-
LoggingSystem.bootstrap(StreamLogHandler.standardError)
30+
private nonisolated(unsafe) var bootstrapLogger = {
31+
LoggingSystem.bootstrap({ _ in StderrLogHandler() })
3132
var log = Logger(label: "com.apple.container")
3233
log.logLevel = .info
3334
return log
3435
}()
3536

36-
public struct Application: AsyncParsableCommand {
37+
public struct Application: AsyncLoggableCommand {
3738
@OptionGroup
38-
var global: Flags.Global
39+
public var logOptions: Flags.Logging
3940

4041
public init() {}
4142

@@ -171,16 +172,16 @@ public struct Application: AsyncParsableCommand {
171172
installRoot: systemHealth.installRoot,
172173
pluginDirectories: pluginDirectories,
173174
pluginFactories: pluginFactories,
174-
log: log
175+
log: bootstrapLogger
175176
)
176177
}
177178

178179
public func validate() throws {
179180
// Not really a "validation", but a cheat to run this before
180181
// any of the commands do their business.
181182
let debugEnvVar = ProcessInfo.processInfo.environment["CONTAINER_DEBUG"]
182-
if self.global.debug || debugEnvVar != nil {
183-
log.logLevel = .debug
183+
if self.logOptions.debug || debugEnvVar != nil {
184+
bootstrapLogger.logLevel = .debug
184185
}
185186
// Ensure we're not running under Rosetta.
186187
if try isTranslated() {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2026 Apple Inc. and the container project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import ArgumentParser
18+
import ContainerAPIClient
19+
import ContainerLog
20+
import Logging
21+
22+
public protocol AsyncLoggableCommand: AsyncParsableCommand {
23+
var logOptions: Flags.Logging { get }
24+
}
25+
26+
extension AsyncLoggableCommand {
27+
/// A shared logger instance configured based on the command's options
28+
public var log: Logger {
29+
var logger = Logger(label: "container", factory: { _ in StderrLogHandler() })
30+
31+
logger.logLevel = logOptions.debug ? .debug : .info
32+
33+
return logger
34+
}
35+
}

Sources/ContainerCommands/BuildCommand.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import NIO
2727
import TerminalProgress
2828

2929
extension Application {
30-
public struct BuildCommand: AsyncParsableCommand {
30+
public struct BuildCommand: AsyncLoggableCommand {
3131
public init() {}
3232
public static var configuration: CommandConfiguration {
3333
var config = CommandConfiguration()
@@ -122,6 +122,9 @@ extension Application {
122122
@Option(name: .long, help: ArgumentHelp("Builder shim vsock port", valueName: "port"))
123123
var vsockPort: UInt32 = 8088
124124

125+
@OptionGroup
126+
public var logOptions: Flags.Logging
127+
125128
@Argument(help: "Build directory")
126129
var contextDir: String = "."
127130

@@ -145,7 +148,7 @@ extension Application {
145148
group.cancelAll()
146149
}
147150

148-
group.addTask { [vsockPort, cpus, memory] in
151+
group.addTask { [vsockPort, cpus, memory, log] in
149152
while true {
150153
do {
151154
let container = try await ClientContainer.get(id: "buildkit")
@@ -166,6 +169,7 @@ extension Application {
166169
try await BuilderStart.start(
167170
cpus: cpus,
168171
memory: memory,
172+
log: log,
169173
progressUpdate: progress.handler
170174
)
171175

Sources/ContainerCommands/Builder/Builder.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
import ArgumentParser
18+
import ContainerAPIClient
1819

1920
extension Application {
20-
public struct BuilderCommand: AsyncParsableCommand {
21+
public struct BuilderCommand: AsyncLoggableCommand {
2122
public init() {}
2223

2324
public static let builderResourceDir = "builder"
@@ -30,5 +31,8 @@ extension Application {
3031
BuilderStop.self,
3132
BuilderDelete.self,
3233
])
34+
35+
@OptionGroup
36+
public var logOptions: Flags.Logging
3337
}
3438
}

Sources/ContainerCommands/Builder/BuilderDelete.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
public struct BuilderDelete: AsyncParsableCommand {
23+
public struct BuilderDelete: AsyncLoggableCommand {
2424
public static var configuration: CommandConfiguration {
2525
var config = CommandConfiguration()
2626
config.commandName = "delete"
@@ -33,7 +33,7 @@ extension Application {
3333
var force = false
3434

3535
@OptionGroup
36-
var global: Flags.Global
36+
public var logOptions: Flags.Logging
3737

3838
public init() {}
3939

Sources/ContainerCommands/Builder/BuilderStart.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ import ContainerizationError
2424
import ContainerizationExtras
2525
import ContainerizationOCI
2626
import Foundation
27+
import Logging
2728
import TerminalProgress
2829

2930
extension Application {
30-
public struct BuilderStart: AsyncParsableCommand {
31+
public struct BuilderStart: AsyncLoggableCommand {
3132
public static var configuration: CommandConfiguration {
3233
var config = CommandConfiguration()
3334
config.commandName = "start"
@@ -45,7 +46,7 @@ extension Application {
4546
var memory: String = "2048MB"
4647

4748
@OptionGroup
48-
var global: Flags.Global
49+
public var logOptions: Flags.Logging
4950

5051
public init() {}
5152

@@ -60,11 +61,11 @@ extension Application {
6061
progress.finish()
6162
}
6263
progress.start()
63-
try await Self.start(cpus: self.cpus, memory: self.memory, progressUpdate: progress.handler)
64+
try await Self.start(cpus: self.cpus, memory: self.memory, log: log, progressUpdate: progress.handler)
6465
progress.finish()
6566
}
6667

67-
static func start(cpus: Int64?, memory: String?, progressUpdate: @escaping ProgressUpdateHandler) async throws {
68+
static func start(cpus: Int64?, memory: String?, log: Logger, progressUpdate: @escaping ProgressUpdateHandler) async throws {
6869
await progressUpdate([
6970
.setDescription("Fetching BuildKit image"),
7071
.setItemsName("blobs"),
@@ -256,6 +257,7 @@ extension Application {
256257
)
257258

258259
try await container.startBuildKit(progressUpdate, taskManager)
260+
log.debug("starting BuildKit and BuildKit-shim")
259261
}
260262
}
261263
}
@@ -278,8 +280,6 @@ extension ClientContainer {
278280
try await process.start()
279281
await taskManager?.finish()
280282
try io.closeAfterStart()
281-
282-
log.debug("starting BuildKit and BuildKit-shim")
283283
} catch {
284284
try? await stop()
285285
try? await delete()

Sources/ContainerCommands/Builder/BuilderStatus.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import ContainerizationExtras
2121
import Foundation
2222

2323
extension Application {
24-
public struct BuilderStatus: AsyncParsableCommand {
24+
public struct BuilderStatus: AsyncLoggableCommand {
2525
public static var configuration: CommandConfiguration {
2626
var config = CommandConfiguration()
2727
config.commandName = "status"
@@ -36,7 +36,7 @@ extension Application {
3636
var quiet = false
3737

3838
@OptionGroup
39-
var global: Flags.Global
39+
public var logOptions: Flags.Logging
4040

4141
public init() {}
4242

Sources/ContainerCommands/Builder/BuilderStop.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
public struct BuilderStop: AsyncParsableCommand {
23+
public struct BuilderStop: AsyncLoggableCommand {
2424
public static var configuration: CommandConfiguration {
2525
var config = CommandConfiguration()
2626
config.commandName = "stop"
@@ -29,7 +29,7 @@ extension Application {
2929
}
3030

3131
@OptionGroup
32-
var global: Flags.Global
32+
public var logOptions: Flags.Logging
3333

3434
public init() {}
3535

Sources/ContainerCommands/Container/ContainerCreate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Foundation
2222
import TerminalProgress
2323

2424
extension Application {
25-
public struct ContainerCreate: AsyncParsableCommand {
25+
public struct ContainerCreate: AsyncLoggableCommand {
2626
public init() {}
2727

2828
public static let configuration = CommandConfiguration(
@@ -45,7 +45,7 @@ extension Application {
4545
var imageFetchFlags: Flags.ImageFetch
4646

4747
@OptionGroup
48-
var global: Flags.Global
48+
public var logOptions: Flags.Logging
4949

5050
@Argument(help: "Image name")
5151
var image: String

Sources/ContainerCommands/Container/ContainerDelete.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
public struct ContainerDelete: AsyncParsableCommand {
23+
public struct ContainerDelete: AsyncLoggableCommand {
2424
public init() {}
2525

2626
public static let configuration = CommandConfiguration(
@@ -35,7 +35,7 @@ extension Application {
3535
var force = false
3636

3737
@OptionGroup
38-
var global: Flags.Global
38+
public var logOptions: Flags.Logging
3939

4040
@Argument(help: "Container IDs")
4141
var containerIds: [String] = []
@@ -82,6 +82,7 @@ extension Application {
8282
var failed = [String]()
8383
let force = self.force
8484
let all = self.all
85+
let logger = log
8586
try await withThrowingTaskGroup(of: String?.self) { group in
8687
for container in containers {
8788
group.addTask {
@@ -97,7 +98,7 @@ extension Application {
9798
print(container.id)
9899
return nil
99100
} catch {
100-
log.error("failed to delete container \(container.id): \(error)")
101+
logger.error("failed to delete container \(container.id): \(error)")
101102
return container.id
102103
}
103104
}

0 commit comments

Comments
 (0)