Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

appmap-swift

Function-call tracing for Swift, with output that converts to AppMap format.

The library has two parts:

  1. AppMap — a @Traced Swift macro plus a small runtime. Annotated functions emit Call/Return trace lines with timing, parameters, task grouping, and parent/child call structure.
  2. appmap-transform — a command-line tool that converts the recorded trace lines into an AppMap JSON file (spec v1.12), viewable with the AppMap extensions for VS Code and JetBrains.
@Traced code  ──►  trace lines  ──►  appmap-transform  ──►  *.appmap.json
                   (os_log or
                    custom handler)

Requirements

  • Swift 6.2+
  • macOS 13+ / iOS 16+ / tvOS 16+ / watchOS 9+ (runtime library)
  • appmap-transform runs on macOS

Installation

Add the package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/getappmap/appmap-swift.git", branch: "main"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "AppMap", package: "appmap-swift"),
    ]),
]

Usage

Annotate individual functions with @Traced, or a whole type with @TracedAll:

import AppMap

@TracedAll
final class OrderService {
    // Every public/internal function with a body is traced automatically.
    func placeOrder(itemId: String, quantity: Int) async throws -> String {
        ...
    }

    // Override: capture an instance property instead of (sensitive) parameters.
    @Traced(captures: ["accountId"])
    func charge(cardNumber: String) async throws {
        ...
    }

    // Override: capture nothing.
    @Traced(captures: [])
    func storeSecret(value: String) {
        ...
    }
}

Notes on capture behavior:

  • By default, every named parameter is recorded via String(describing:).
  • captures: takes a list of expressions evaluated in the function body, so bare identifiers resolve to parameters or self properties.
  • captures: [] records no values — use it for functions whose parameters carry sensitive content.
  • @TracedAll skips private/fileprivate functions (add an explicit @Traced to opt one back in) and functions that already have @Traced.

Async calls are grouped: a task-local trace context propagates the task id and parent call id across await boundaries and into child tasks, so nested calls form a tree.

Enabling and routing output

Tracing defaults to on in DEBUG builds, off in release. Configure it explicitly at startup:

import AppMap

// Default output: the unified logging system, subsystem "dev.appmap.trace".
TraceCollector.shared.configure(enabled: true)

// Or route lines yourself (stdout, a file, a network sink, ...):
TraceCollector.shared.configure(enabled: true, output: { line in print(line) })

Recording and converting to AppMap

Custom handler — collect the lines however you like, then:

appmap-transform --name my-recording < trace.log > my-recording.appmap.json

Unified logging — capture from a device/simulator run, then convert. The transform scans each line for the [TRACE ...] markers, so the log command's line prefixes don't need to be stripped:

# Live capture from the booted simulator:
xcrun simctl spawn booted log stream --style compact \
    --predicate 'subsystem == "dev.appmap.trace"' > trace.log

# Or after the fact, from the Mac itself:
log show --last 5m --style compact \
    --predicate 'subsystem == "dev.appmap.trace"' > trace.log

appmap-transform --name my-recording --app MyApp -o my-recording.appmap.json < trace.log

Options: --name sets the recording name shown in AppMap viewers, --app sets the application name, -o writes to a file instead of stdout.

Open the resulting .appmap.json with the AppMap extension for VS Code or JetBrains.

Example

Examples/AppMapExample is a runnable end-to-end demo:

make example
# or, by hand:
swift run appmap-example | swift run appmap-transform --name example > example.appmap.json

Building and testing

make build     # swift build
make test      # swift test (macro expansion + runtime behavior tests)
make example   # build and run the demo pipeline → example.appmap.json
make clean     # swift package clean and remove example.appmap.json

Trace line format

Three line types, key=value pairs, string values double-quoted:

[TRACE Component] id="OrderService#placeOrder" class="OrderService" function="placeOrder" file="MyApp/OrderService.swift" line=12 static=false
[TRACE Call] id=1 timestamp="2026-08-25T12:00:00.000Z" task_id=8412... parent=0 component="OrderService#placeOrder" itemId="apple"
[TRACE Return] id=2 callId=1 elapsed=0.014210 exception=nil
  • Component is emitted once per function, on its first call.
  • task_id groups calls belonging to one logical task tree; the transform maps it to the AppMap thread_id.
  • parent is present when the call was made inside another traced call.
  • exception is nil or "ErrorType: message".

Known limitations

  • Captured parameter values containing double quotes can confuse the line parser; the affected value may be truncated at the embedded quote.
  • Return values are not recorded (only timing and exceptions).
  • Concurrent child tasks (async let, task groups) share their parent's thread_id. The transform rebuilds the call tree from the recorded parent= ids, so nesting is always correct — but overlapping sibling calls are serialized in the output, since AppMap represents each thread as a simple call stack. Wall-clock overlap between siblings is not shown.
  • os_log truncates long messages; prefer a custom output handler when capturing large parameter values.

License

MIT

About

AppMap recording library for Swift, based on macros

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages