Giter Club home page Giter Club logo

Comments (6)

JacksonWeber avatar JacksonWeber commented on May 27, 2024

@mderriey Thank you for submitting this issue, but I'm having some trouble reproducing your results. I have a simple app currently that looks like this:

const express = require('express');
let appInsights = require("applicationinsights");
const winston = require('winston');

const logger = winston.createLogger({
  transports: [
      new winston.transports.Console({
          level: 'error',
          format: winston.format.simple(),
          debugStdout: true,
      })
  ]
});

appInsights.setup("<YOUR-CONNECTION-STRING-HERE>")
.setAutoCollectConsole(true, true)
.start();

class InvariantError extends Error {}
const app = express();
const PORT = 8080;

async function setupRoutes() {
  app.get('/', async (req, res) => {
    logger.error(`Unhandled error: ${new InvariantError('This does not appear in App Insights')}`);
    logger.error(`Unhandled error: ${new Error('This does appear in App Insights')}`);
    res.send('Hello World!');
  });
}

setupRoutes().then(() => {
  app.listen(PORT);
  console.log(`Listening on http://localhost:${PORT}`);
});

And I'm seeing both errors' messages parsed and output to ApplicationInsights. Please let me know if this sample produces the same results for you. If not, let me know some more details of the sample application you're testing with and what version of the beta you're using.

from applicationinsights-node.js.

mderriey avatar mderriey commented on May 27, 2024

Hey @JacksonWeber, thanks for getting back to me.

I can see two differences between your setup and mine:

  1. Yours doesn't seem to be using the beta version of the App Insights SDK.
  2. We don't log errors the same way, I think with your sample the template literal means that the JS runtime converts the Error into a string, not the App Insights SDK. Can you please try this way?
    const express = require('express');
    let appInsights = require("applicationinsights");
    const winston = require('winston');
    
    const logger = winston.createLogger({
      transports: [
          new winston.transports.Console({
              level: 'error',
              format: winston.format.simple(),
              debugStdout: true,
          })
      ]
    });
    
    appInsights.setup("<YOUR-CONNECTION-STRING-HERE>")
    .setAutoCollectConsole(true, true)
    .start();
    
    class InvariantError extends Error {}
    const app = express();
    const PORT = 8080;
    
    async function setupRoutes() {
      app.get('/', async (req, res) => {
    -   logger.error(`Unhandled error: ${new InvariantError('This does not appear in App Insights')}`);
    -   logger.error(`Unhandled error: ${new Error('This does appear in App Insights')}`);
    +   logger.error('Unhandled error', { error: new InvariantError('This does not appear in App Insights') });
    +   logger.error('Unhandled error', { error: new Error('This does appear in App Insights') }));
        res.send('Hello World!');
      });
    }
    
    setupRoutes().then(() => {
      app.listen(PORT);
      console.log(`Listening on http://localhost:${PORT}`);
    });

Thanks

from applicationinsights-node.js.

JacksonWeber avatar JacksonWeber commented on May 27, 2024

@mderriey Apologies, I had my test app in shim-testing configuration by accident there, thanks!
However, I'm still able to see the message in the InvariantError in the CustomProperties of ApplicationInsights on version 11 of the Beta package.
image
I'm using the sample app above just reconfigured to use the beta package properly and enabling the winston instrumentation option and able to see both errors serialized.

Assuming you're using the same logger.error calls to create Winston logs, can I see how you're initializing/configuring Azure Monitor?

from applicationinsights-node.js.

mderriey avatar mderriey commented on May 27, 2024

How weird... 🤔

Assuming you're using the same logger.error calls to create Winston logs

We are, yes, except we use the JSON format for the console transport, but that shouldn't have any impact from my understanding given diagnostic-channel-publishers injects its own transport.

can I see how you're initializing/configuring Azure Monitor?

Sure thing.

Click to expand
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { useAzureMonitor } from 'applicationinsights'
import { SetTenantAliasAttributeSpanProcessor } from './tracing/set-tenant-alias-attribute-span-processor'
import { TruncateDbStatementAttributeSpanProcessor } from './tracing/truncate-db-statement-attribute-span-processor'
import type { ProxyTracerProvider } from '@opentelemetry/api'
import { trace } from '@opentelemetry/api'
import type { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import type { InstrumentationOptions as ApplicationInsightsInstrumentationOptions } from 'applicationinsights/out/src/types'
import type { InstrumentationOptions as OpenTelemetryInstrumentationOptions } from '@azure/monitor-opentelemetry'
import { instrumentations } from './tracing/instrumentations'

// Default attributes set on the OpenTelemetry traces
const resource = Resource.EMPTY
resource.attributes[SemanticResourceAttributes.SERVICE_NAME] = '<redacted>'
resource.attributes[SemanticResourceAttributes.SERVICE_NAMESPACE] = '<redacted>'

useAzureMonitor({
  resource,
  instrumentationOptions: {
    azureSdk: { enabled: false },
    http: { enabled: false },
    mongoDb: { enabled: false },
    mySql: { enabled: false },
    postgreSql: { enabled: false },
    redis: { enabled: false },
    redis4: { enabled: false },
    // NOTE: only winston is enabled
    winston: { enabled: true },
    console: { enabled: false },
    bunyan: { enabled: false },
  } satisfies ApplicationInsightsInstrumentationOptions as unknown as OpenTelemetryInstrumentationOptions,
})

const tracerProvider = (trace.getTracerProvider() as ProxyTracerProvider).getDelegate() as NodeTracerProvider

// NOTE: a couple of custom processors
tracerProvider.addSpanProcessor(new SetTenantAliasAttributeSpanProcessor())
tracerProvider.addSpanProcessor(new TruncateDbStatementAttributeSpanProcessor())

registerInstrumentations({
  tracerProvider,
  // NOTE: these are additional OTel instrumentations like HTTP, Express, Redis, tedious.
  instrumentations,
})

When I initially looked into this, I found the problem came from the fact that the custom classes that inherit Error have their constructor.name property that is different from Error, see

// Serialize Error objects as strings to avoid serialization errors
if (value?.constructor.name === "Error") {

If I run this in my Node.js REPL, that's what I get:

λ node
Welcome to Node.js v20.11.1.
Type ".help" for more information.
> const { InvariantError } = require('./src/util/invariant')
undefined
> new InvariantError().constructor.name
'InvariantError'

I also confirmed that we're using 3.0.0-beta.11:

λ npm ls applicationinsights
<redacted>@1.0.0 <redacted>
`-- [email protected]

I also just tested it again, and the error isn't serialized correctly.

For completeness, here's the TypeScript code:

export function invariant(condition: any, message: string): asserts condition {
  if (condition) return
  throw new InvariantError(message)
}

export class InvariantError extends Error {}

In a request handler, I added the following bogus invariant assertion:

invariant(1 === 2, 'Boo')

The code that logs these errors is:

logger.error('Unhandled error', {
  error: unwrappedError,
})

I put a breakpoint on that line, and evaluated unwrappedError.constructor.name, which gave back InvariantError.

Here's the result in the portal:

image

from applicationinsights-node.js.

JacksonWeber avatar JacksonWeber commented on May 27, 2024

I spent some time investigating this and want to note a few things.

I was able to recreate your experience with the error object being logged as an empty object. However, from messing with the definition of the InvariantError I was able to get the error to properly be serialized in the customDimensions.

So if I defined my custom error class like:

export class InvariantError extends Error {
    constructor(message: string) {
        super(message);
        Object.setPrototypeOf(this, InvariantError.prototype);
    }
};

Adjusting the prototype accordingly so that the object properly identifies as an InvariantError, I do get an "error": {} logged in ApplicationInsights. However, I was able to avoid this by setting class properties on the InvariantError, ex:

export class InvariantError extends Error {
    constructor(message: string) {
        super();
        this.name = "InvariantError";
        this.message = message;
        Object.setPrototypeOf(this, InvariantError.prototype);
    }
};

Hopefully this works as a workaround for the current released version (beta 11), however in testing and investigation I discovered that as a result of #1272, Winston logs will be sent to logger.emit() exposed by the OpenTelemetry API without first being properly formatted. I have a PR here: #1297 to resolve that issue, and this should be fixed for you in the upcoming release. Tested with custom errors, standard errors, and other objects.

from applicationinsights-node.js.

mderriey avatar mderriey commented on May 27, 2024

@JacksonWeber thanks a lot for your investigation.

I'm curious to know why "my" InvariantErrors showed up as empty objects without any additional changes, whereas you had to change the prototype of the object...

Anyway, thanks a lot, we'll test the workaround where we assign properties directly on our error classes instead of relying on the base class constructor.

from applicationinsights-node.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.