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

Request ID logging #3

Open
mnapoli opened this issue Mar 13, 2020 · 6 comments
Open

Request ID logging #3

mnapoli opened this issue Mar 13, 2020 · 6 comments
Labels
enhancement New feature or request

Comments

@mnapoli
Copy link
Member

mnapoli commented Mar 13, 2020

Here are some notes about logging with the AWS request ID.

The goal

Be able to see all logs of a single invocation.

How? By filtering using the "request ID".

How? By storing that request ID in each log record.

Is it not there already? It's there when logging from a JS lambda for example.

When logging from PHP (by writing to stdout/stderr), only the START, END and REPORT log lines contain it (the lines written by Lambda itself).

NodeJS runtime

exports.handler = async () => {
    console.log('Hello from Lambda!');
};

This is what we see in CloudWatch:

Capture d’écran 2020-03-13 à 22 38 05

This is the same thing in Insights:

Capture d’écran 2020-03-13 à 22 39 49

Analysis: the request ID is added in a non-structured text line. Values are separated by tabs (\t). For example:

2020-03-13T21:21:56.341Z 0879de55-0817-4d5c-9f37-7ed2755a72e1 INFO Hello from Lambda! 

Current Bref PHP runtime

echo 'Hello world';

In CloudWatch:

Capture d’écran 2020-03-13 à 22 45 48

In Insights:

Capture d’écran 2020-03-13 à 22 47 35

Analysis: the @requestId field (or the request ID value itself) does not appear in that record.

Experiment 1

echo json_encode([
        'message' => 'Hello world!',
        'requestId' => $context->getAwsRequestId(),
        '@requestId' => $context->getAwsRequestId(),
]) . PHP_EOL;

In CloudWatch:

Capture d’écran 2020-03-13 à 22 40 59

In Insights:

Capture d’écran 2020-03-13 à 22 42 14

Analysis: it looked promising at first, but it turns out the @requestId field is escaped to @@requestId by CloudWatch (or Lambda?).

That means that we still have no @requestId field. For example, this query does not return the lines we logged in PHP:

fields @message
| filter @requestId = '6dc9b447-3898-4793-83cf-60730058c833'

Experiment 2

Based on the tabulated format from the JS runtime, could it be that CloudWatch interprets data that way?

echo "2020-03-13T21:21:56.341Z\t{$context->getAwsRequestId()}\tINFO\tHello world" . PHP_EOL;

CloudWatch:

Capture d’écran 2020-03-13 à 22 50 17

Insights:

Capture d’écran 2020-03-13 à 22 52 37

Analysis: OMG it works!!!

The field is correctly recognized now:

Capture d’écran 2020-03-13 à 22 54 54

Even serverless logs parsed the fields:

Capture d’écran 2020-03-13 à 22 56 35

@mnapoli mnapoli added the enhancement New feature or request label Mar 13, 2020
@mnapoli
Copy link
Member Author

mnapoli commented Mar 13, 2020

Experiment 3

echo $context->getAwsRequestId() . "\tHello world\n";

CloudWatch:

Capture d’écran 2020-03-13 à 22 58 19

Insights:

Capture d’écran 2020-03-13 à 22 57 57

Analysis: worked too! The log message is simpler, easier to read as well. However it's not colored in the terminal by serverless logs:

Capture d’écran 2020-03-13 à 22 58 52

The serverless logs color come from here: https://github.com/serverless/serverless/blob/f822ae7e4c4062178b9f6843104fb33f0a586ef5/lib/plugins/aws/utils/formatLambdaLogEvent.js#L21-L43

@mnapoli
Copy link
Member Author

mnapoli commented Mar 13, 2020

Experiment 4

Same as experiment 3, but in the case where we want to log structured data with JSON:

echo "{$context->getAwsRequestId()}\t" . json_encode([
    'message' => 'This is a message',
]) . PHP_EOL;

(yes we mix string and JSON content, CloudWatch supports it on purpose)

Capture d’écran 2020-03-13 à 23 09 45
Capture d’écran 2020-03-13 à 23 08 51

Seems to work perfectly as well!

Just tested on NodeJS as well to confirm, they do the same thing:

2020-03-13T22:12:01.928Z	206e2f13-4cc3-415b-8ed2-a5b108111680	INFO	{ message: 'Hello from Lambda!' }

@mnapoli mnapoli changed the title JSON logging Request ID logging Mar 14, 2020
@mnapoli
Copy link
Member Author

mnapoli commented Mar 14, 2020

OK now the difficulty is: the request ID is scoped at the invocation level.

The logger is scope at the application level.

How do we combine this properly 🤔

mnapoli added a commit to brefphp/bref that referenced this issue Mar 14, 2020
See brefphp/logger#3: we are trying to copy the log format of other AWS runtimes.

Having the request ID in the log line lets use easily filter logs to find all the logs of a single invocation.
@mnapoli
Copy link
Member Author

mnapoli commented Mar 14, 2020

For reference, here is JS code that is responsible for formatting console.log() in the NodeJS runtime:

function patchLogging(invokeId) {
    console.log = console.error = console.warn = console.info = function prettyConsoleLog() {
        var dateString = new Date().toISOString();
        var message = `${dateString}\t${invokeId}\t${util.format.apply(this, arguments)}\n`;
        awslambda.sendConsoleLogs(message, Buffer.byteLength(message));
    };
};

Digging further:

sendConsoleLogs: consoleLog,
function formatConsole(str) {
  return str.replace(/^[0-9TZ:.-]+\t[0-9a-f-]+\t/, '\u001b[34m$&\u001b[0m')
}

function consoleLog(str) {
  process.stderr.write(formatConsole(str))
}

function systemLog(str) {
  process.stderr.write(formatSystem(str) + '\n')
}

function systemErr(str) {
  process.stderr.write(formatErr(str) + '\n')
}

function handleResult(resultStr, cb) {
  if (!process.stdout.write('\n' + resultStr + '\n')) {
    process.stdout.once('drain', cb)
  } else {
    process.nextTick(cb)
  }
}

So it is reassuring: the NodeJS runtime indeed logs via stderr, just like we do in PHP.

For a long time I thought they used a secret technique for logging with the @requestId variable, but it seems to be AWS CloudWatch that has a secret behavior of parsing the \t separated values. Hooray 🎉

@mnapoli
Copy link
Member Author

mnapoli commented Mar 14, 2020

Before dealing with all the log lines, we can start applying all that to exceptions/errors: see brefphp/bref#590

@ctessmer
Copy link

ctessmer commented May 10, 2023

@mnapoli I love how thorough you are in your notes! And that you make them public.

I just started using Github as a place to document my personal journey through issues in my private repos (as you know). At first I thought, "Oh man Christian, this is too much. You're wasting time."

But I know the the future me will really love this when I inevitably forget how to configure a serverless setting 2+ years from now.

I guess what I'm saying is thank you for:

  1. Affirming, indirectly, that I'm not crazy for doing the same and
  2. For writing this up.

Watching someone else solve problems in "real time" helps me learn to be a better problem solver.

Excellent work as always man! 💯

lightningcraft-0201 added a commit to lightningcraft-0201/Ref that referenced this issue Oct 16, 2023
See brefphp/logger#3: we are trying to copy the log format of other AWS runtimes.

Having the request ID in the log line lets use easily filter logs to find all the logs of a single invocation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants