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

Introduce task local logger #315

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

FranzBusch
Copy link
Member

Motivation

Structured logging and structured concurrency are a match made in heaven. Using task locals for loggers enables easier passing of metadata for log messages and removes the cumbersome passing around of loggers.

Modification

This PR adds a proposal, the implementation and benchmarks for a task local logger.

Result

Using a task local for logging makes it incredible ergonomic to carry around metadata. To be transparent the benchmarks show that this is around 20x slower than a bare no-op log. A no-op log takes around 2ns whereas a task local logger no-op log takes around 40ns. 95% of that time is spent in accessing the task local since that is missing some @inlinable in the runtime and goes through the C interface. Instruction wise it is 20 vs 290 instructions.

Now I still propose that we go forward with this since the ergonomics far outweigh the performance initially. Furthermore, this acts as a great forcing function for performance improvements in the runtime since logging is often done in hot paths.

NoOpLogger
╒══════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕
│ Metric                   │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │
╞══════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡
│ Instructions *           │      20 │      20 │      20 │      20 │      20 │      20 │      20 │     100 │
├──────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Malloc (total) *         │       0 │       0 │       0 │       0 │       0 │       0 │       0 │     100 │
├──────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Time (wall clock) (ns) * │       2 │       2 │       2 │       2 │       2 │       2 │       2 │     100 │
╘══════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛

NoOpLogger task local
╒══════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕
│ Metric                   │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │
╞══════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡
│ Instructions *           │     289 │     289 │     289 │     289 │     289 │     290 │     290 │     100 │
├──────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Malloc (total) *         │       0 │       0 │       0 │       0 │       0 │       0 │       0 │     100 │
├──────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Time (wall clock) (ns) * │      33 │      39 │      39 │      40 │      40 │      42 │      42 │     100 │
╘══════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛

@FranzBusch FranzBusch force-pushed the fb-task-local-logger branch 2 times, most recently from 13be449 to 0e33370 Compare August 8, 2024 12:55
Copy link
Member

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

I’m currently offline for a week and would really want to have the time to dive deep on this. I don’t think it’s an obvious great idea and needs some proper debate and plans before merging. I’m especially concerned what this means for the library ecosystem and will have to think this through more.

Requesting changes to mark this intent. Thanks for your patience

@@ -43,6 +43,9 @@ import WASILibc
/// logger.info("Hello World!")
/// ```
public struct Logger {
@available(macOS 10.15, *)
@TaskLocal
public static var logger: Logger = Logger(label: "NoOp", SwiftLogNoOpLogHandler())

Choose a reason for hiding this comment

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

I'd suggest a name like current or inherited so that it reads more naturally than Logger.logger (which sounds like a factory method that creates a brand new logger instead)

Copy link

Choose a reason for hiding this comment

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

I concur with this completely.

Copy link
Member

Choose a reason for hiding this comment

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

Agree! Logger.current or Logger.inherited both sound good to me but open to other stuff too

Copy link
Member

@weissi weissi left a comment

Choose a reason for hiding this comment

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

I think that's great but I know @ktoso would like to discuss. So I'm approving but only because I know that @ktoso has already requested changesdiscussion

@@ -43,6 +43,9 @@ import WASILibc
/// logger.info("Hello World!")
/// ```
public struct Logger {
@available(macOS 10.15, *)
@TaskLocal
public static var logger: Logger = Logger(label: "NoOp", SwiftLogNoOpLogHandler())
Copy link
Member

Choose a reason for hiding this comment

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

Agree! Logger.current or Logger.inherited both sound good to me but open to other stuff too

Copy link
Contributor

@0xTim 0xTim left a comment

Choose a reason for hiding this comment

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

Overall LGTM I have concerns about it being enforced and not opt-in (though I guess one could pin to the old version). It's good to force performance improvements in the runtime but with such a performance hit being shown I wonder if it's worth offering a way of not using it - ignore me, it's entirely opt in if you just create an instance of the logger and pass that around 🤦 don't review code at 3AM 😆


var logger = Logger.logger
logger[metadataKey: "MetadataKey1"] = "Value1"
Logger.$logger.withValue(logger) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this syntax required for passing metadata through to child tasks? I thought that was automatic. E.g.:

logger[metadataKey: "MetadataKey1"] = "Value1"
try await someFunction()


func someFunction() async throws {
  // `MetadataKey1` is available here
}

Definitely agree with renaming logger here - the API doesn't come across great with the double logger in this example

Choose a reason for hiding this comment

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

Logger is a value type, so the modified copy needs to be written back into the task local to be propagated to child tasks.

# Motivation

Structured logging and structured concurrency are a match made in heaven. Using task locals for loggers enables easier passing of metadata for log messages and removes the cumbersome passing around of loggers.

# Modification

This PR adds a proposal, the implementation and benchmarks for a task local logger.

# Result

Using a task local for logging makes it incredible ergonomic to carry around metadata. To be transparent the benchmarks show that this is around 20x slower than a bare no-op log. A no-op log takes around 2ns whereas a task local logger no-op log takes around 40ns. 95% of that time is spent in accessing the task local since that is missing some `@inlinable` in the runtime and goes through the C interface. Instruction wise it is 20 vs 290 instructions.

Now I still propose that we go forward with this since the ergonomics far outweigh the performance initially. Furthermore, this acts as a great forcing function for performance improvements in the runtime since logging is often done in hot paths.
@ktoso ktoso added the kind/proposal Proposals for review. label Aug 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/proposal Proposals for review.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants