Node.js-taustakehityksen bootcamp · Oppitunti

Rakenteinen lokitus korrelaatiotunnisteilla

Tuota koneellisesti jäsennettäviä JSON-lokeja ja välitä korrelaatiotunnisteita asynkronisessa kontekstissa pyyntöjen jäljittämistä varten.

Oppitunti 1/413 vaihetta

Rakenteinen lokitus korrelaatiotunnisteilla on ilmainen Node.js-taustakehityksen bootcamp-oppitunti CoddyKitissä. Tämä on oppitunti 1/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Node.js-taustakehityksen bootcamp-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.

Miksi rakenteinen lokitus

Tuotantoympäristössä koneet lukevat lokit ennen ihmisiä. Rivi kuten console.log('User ' + id + ' failed login') on vapaamuotoinen merkkijono: sen löytämiseksi myöhemmin on kirjoitettava hauraat säännölliset lausekkeet, eikä lokitietoja voi koostaa tai suodattaa luotettavasti.

Rakenteinen lokitus tuottaa jokaisen lokimerkinnän JSON-objektina, jolla on yhdenmukaiset kentät. Lokien koontipalvelut (Loki, Elasticsearch, Datadog, CloudWatch) indeksoivat nämä kentät, joten voitte hakea ehdolla level="error" AND userId=42 välittömästi.

  • Vapaamuotoinen: helppo kirjoittaa, hankala hakea.
  • Rakenteinen: yksi JSON-objekti riviä kohden (NDJSON), jonka jäsentäminen on suoraviivaista.

Minimaalinen JSON-lokikirjuri

Perusidea on yksinkertainen: rakennetaan tavallinen objekti, lisätään siihen taso ja aikaleima ja kirjoitetaan yksi JSON-merkkijono riviä kohden stdoutiin. Ajoympäristö tai kontti kerää stdoutin ja toimittaa sen lokien koontipalveluun.

Jokainen lokimerkintä on yksi kelvollisen JSON:n sisältävä rivi. Tätä muotoa kutsutaan NDJSON:ksi (newline-delimited JSON), ja kaikki nykyaikaiset lokien taustajärjestelmät ymmärtävät sitä.

function log(level, message, fields = {}) {
  const entry = {
    level,
    time: new Date().toISOString(),
    message,
    ...fields,
  };
  process.stdout.write(JSON.stringify(entry) + '\n');
}

log('info', 'server started', { port: 3000 });
log('error', 'login failed', { userId: 42, reason: 'bad_password' });

Käyttäkää oikeaa lokikirjuria: pino

Itse tehty toteutus sopii oppimiseen, mutta tuotantosovelluksissa käytetään perusteellisesti testattua lokikirjuria. pino on Node.js:n de facto -valinta: se on erittäin nopea, koska se sarjallistaa JSON:n työntekijäystävällisellä tavalla ja kirjoittaa asynkronisesti.

  • logger.info(obj, msg) — ensimmäinen argumentti on kentät sisältävä objekti ja toinen viestimerkkijono.
  • Tasot: trace, debug, info, warn, error, fatal.
  • Tuotannossa stdout välitetään pino-pretty-ohjelmalle vain kehitysympäristössä; tuotantoon lähetetään raakaa JSON:ää.
const pino = require('pino');
const logger = pino({ level: 'info' });

logger.info({ port: 3000 }, 'server started');
logger.error({ userId: 42, reason: 'bad_password' }, 'login failed');

// Child loggers bind fields onto every subsequent log:
const reqLog = logger.child({ requestId: 'abc-123' });
reqLog.info('handling request');

Ongelma: yhden pyynnön jäljittäminen

Kuormituksen alla satojen pyyntöjen lokimerkinnät sekoittuvat keskenään. Kun käyttäjä 42 ilmoittaa virheestä, teidän on nähtävä jokainen hänen pyyntöönsä kuuluva lokirivi — middleware-välikerroksen, palveluiden ja tietokantakutsujen ajalta.

Ratkaisu on korrelaatiotunniste (josta käytetään myös nimitystä request ID tai trace ID): pyyntöä kohden kerran luotava yksilöivä tunniste, joka liitetään jokaiseen kyseistä pyyntöä käsiteltäessä tuotettuun lokimerkintään.

  • Kun haette tunnisteella correlationId="7f3a...", koontipalvelu kokoaa koko pyynnön aikajanan.
  • Jos tunniste saapuu tulevan pyynnön otsakkeessa, voitte yhdistää lokitiedot palveluiden välillä.

Tunnisteen luominen ja hyväksyminen

Lukekaa järjestelmän rajalla saapuva korrelaatio-otsake, jos kutsuja (yhdyskäytävä tai ylävirran palvelu) on jo asettanut sellaisen; muutoin luokaa uusi UUID. Palauttakaa se aina myös vastauksessa, jotta asiakkaat ja välityspalvelimet voivat tallentaa sen.

Vakiintuneita otsakkeiden nimiä ovat x-request-id ja x-correlation-id. Saapuvan tunnisteen uudelleenkäyttö mahdollistaa jäljityksen palvelurajojen yli.

const { randomUUID } = require('crypto');

function correlationMiddleware(req, res, next) {
  const incoming = req.headers['x-correlation-id'];
  const correlationId = incoming || randomUUID();
  req.correlationId = correlationId;
  res.setHeader('x-correlation-id', correlationId);
  next();
}

module.exports = { correlationMiddleware };

Naiivi lähestymistapa ja sen ongelmat

Ilmeinen ratkaisu on välittää req.correlationId argumenttina jokaiseen funktioon ja jokaiseen lokikutsuun. Tämä toimii, mutta ei skaalaudu: syvä kutsupino (ohjain → palvelu → repositorio → apufunktio) pakottaa välittämään tunnisteen funktioiden läpi, vaikka niillä ei muutoin olisi mitään syytä tietää siitä.

Tämä on taustajärjestelmän prop drilling -ongelma. Tunnisteen pitäisi olla implisiittisesti kaikkien pyynnön aikana suoritettavien koodiosien käytettävissä — ilman kaikkien funktioiden allekirjoitusten muuttamista.

AsyncLocalStorage apuna

Node.js sisältää AsyncLocalStorage-ominaisuuden (async_hooks-moduulissa). Se luo säilön, joka pysyy sidottuna nykyiseen asynkroniseen suoritusympäristöön — säilyen await-lauseiden, callbackien, ajastimien ja promisejen yli — ilman että samanaikaiset pyynnöt jakavat sitä.

Ajatelkaa sitä pyyntökohtaisena säiekohtaisena tallennustilana. Kutsukaa als.run(store, callback) kerran pyyntöä kohden; missä tahansa kyseisen callbackin sisällä (riippumatta kutsupinon syvyydestä tai myöhemmin suoritettujen await-lauseiden määrästä) als.getStore() palauttaa saman säilön.

const { AsyncLocalStorage } = require('async_hooks');
const als = new AsyncLocalStorage();

async function deep() {
  await new Promise((r) => setTimeout(r, 10));
  // Same store, even after awaits and timers:
  return als.getStore().correlationId;
}

async function main() {
  await als.run({ correlationId: 'req-1' }, async () => {
    console.log('inside:', await deep());
  });
  console.log('outside:', als.getStore());
}

main();

AsyncLocalStoragen liittäminen pyyntöön

Korvatkaa naiivi middleware: sen sijaan että liittäisitte tunnisteen req-olioon, suorittakaa pyynnön loppuosa als.run()-kutsun sisällä ja tallentakaa tunniste säilöön. Nyt jokainen tämän pyynnön aikana suoritettava koodirivi — ohjaimet, palvelut ja tietokantacallbackit — voi käyttää tunnistetta getStore()-kutsulla.

On olennaista kutsua next() run-callbackin sisällä, jotta seuraavat käsittelijät perivät suoritusympäristön.

const { AsyncLocalStorage } = require('async_hooks');
const { randomUUID } = require('crypto');

const als = new AsyncLocalStorage();

function context() {
  return als.getStore() || {};
}

function correlationMiddleware(req, res, next) {
  const correlationId = req.headers['x-correlation-id'] || randomUUID();
  res.setHeader('x-correlation-id', correlationId);
  als.run({ correlationId }, () => next());
}

module.exports = { als, context, correlationMiddleware };

Tunnisteen lisääminen automaattisesti jokaiseen lokiin

Hyöty on seuraava: käärikää logger niin, että se lukee korrelaatiotunnisteen automaattisesti AsyncLocalStorage-säilöstä. Sovelluskoodi kutsuu log.info('saved order') ilman tunnisteargumenttia, mutta jokainen tuotettu lokirivi sisältää silti oikean correlationId-kentän.

pino-kirjastossa tämä ilmaistaan deklaratiivisesti mixin-asetuksella, joka yhdistää ylimääräiset kentät jokaiseen lokitietueeseen kirjoitushetkellä.

const pino = require('pino');
const { als } = require('./context');

const logger = pino({
  level: 'info',
  mixin() {
    const store = als.getStore();
    return store ? { correlationId: store.correlationId } : {};
  },
});

// Anywhere deep in the request, no ID passed explicitly:
function saveOrder(order) {
  logger.info({ orderId: order.id }, 'order saved');
}

module.exports = { logger, saveOrder };

Tunnisteen välittäminen alavirran kutsuihin

Korrelaatio ulottuu palveluiden välille vain, jos tunniste välitetään lähtevissä pyynnöissä. Kun palvelu kutsuu toista HTTP-sovellusliittymää, lukekaa tunniste suoritusympäristöstä ja asettakaa se otsakkeeksi. Alavirran palvelun middleware käyttää samaa tunnistetta, joten molempien palveluiden lokit jakavat yhden tunnisteen.

Sama periaate koskee viestijonoja (tallentakaa tunniste viestin metatietoihin) ja taustatöitä (tallentakaa se työn hyötykuormaan).

const { context } = require('./context');

async function callInventoryService(sku) {
  const { correlationId } = context();
  const res = await fetch('https://inventory.internal/check', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-correlation-id': correlationId,
    },
    body: JSON.stringify({ sku }),
  });
  return res.json();
}

module.exports = { callInventoryService };

Tuotantoympäristön hyvät käytännöt

Muutama sääntö pitää rakenteiset lokit siisteinä ja turvallisina:

  • Älkää koskaan lokittako salaisuuksia tai henkilötietoja — salasanoja, tokeneita tai täydellisiä korttinumeroita. Määrittäkää pinon redact-polut (esimerkiksi ['req.headers.authorization', '*.password']).
  • Lokittakaa oikealla tasolla — info liiketoimintatapahtumille, warn palautuville ongelmille ja error virheille serialisoidun virheolion kanssa.
  • Pitäkää kenttien nimet vakaina — correlationId tarkoittaa aina samaa asiaa; koontinäytöt ja hälytykset riippuvat siitä.
  • Yksi JSON-objekti riville — älkää muotoilko tulostetta tuotannossa ihmislukuiseksi, koska se rikkoo NDJSON-jäsennyksen.
const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  redact: ['req.headers.authorization', 'password', '*.password', 'creditCard'],
});

logger.info({ user: { id: 7, password: 'hunter2' } }, 'login');
// -> password is replaced with [Redacted] in the output

Pikatarkistus

Korrelaatiotunnisteen on oltava käytettävissä jokaisessa funktiona syvässä kutsupinossa pyynnön aikana ilman, että sitä välitetään argumenttina tai että se vuotaa samanaikaisten pyyntöjen välillä. Mikä on oikea mekanismi Node.js:ssä?

Kertaus

Opitte tekemään lokeista sekä koneellisesti jäsennettäviä että jäljitettäviä:

  • Rakenteinen lokitus tuottaa yhden JSON-objektin riville (NDJSON), jotta koontijärjestelmät voivat indeksoida ja hakea kenttiä.
  • pino on Node.js:n vakiintunut nopea logger; kenttäobjekti tulee ensin ja viesti toisena.
  • Korrelaatiotunniste luodaan tai otetaan saapuvasta otsakkeesta kerran pyyntöä kohden, ja se palautetaan vastauksessa.
  • AsyncLocalStorage välittää tunnisteen koko asynkronisen kutsupinon läpi muuttamatta funktioiden allekirjoituksia ja eristää samanaikaiset pyynnöt toisistaan.
  • Pinon mixin lisää tunnisteen automaattisesti jokaiseen lokiin; sen välittäminen lähtevänä otsakkeena laajentaa jäljityksen palveluiden välille.
  • Noudattakaa hyviä käytäntöjä: redact-toiminnolla peitetään salaisuudet, käyttäkää oikeita lokitasoja, pitäkää kenttien nimet vakaina älkääkä muotoilko tulostetta ihmislukuiseksi tuotannossa.
Aloita maksutta

Opi JavaScript tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
22
Oppitunnit
92

Usein kysytyt kysymykset

Onko oppitunti ”Rakenteinen lokitus korrelaatiotunnisteilla” ilmainen?

Kyllä – oppitunnin ”Rakenteinen lokitus korrelaatiotunnisteilla” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Node.js-taustakehityksen bootcamp-kurssin, päivitä CoddyKit PROhon. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Rakenteinen lokitus korrelaatiotunnisteilla”?

Tuota koneellisesti jäsennettäviä JSON-lokeja ja välitä korrelaatiotunnisteita asynkronisessa kontekstissa pyyntöjen jäljittämistä varten. Harjoittelet Node.js-taustakehityksen bootcamp-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Node.js-taustakehityksen bootcamp-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Node.js-taustakehityksen bootcamp-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 1/4.

Kuinka kauan ”Rakenteinen lokitus korrelaatiotunnisteilla”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Node.js-taustakehityksen bootcamp-oppitunnilla?

Kyllä. Jokainen Node.js-taustakehityksen bootcamp-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Rakenteinen lokitus korrelaatiotunnisteilla
  2. Hajautettu jäljitys OpenTelemetry-spanien avulla
  3. Sovellusmetriikoiden julkaiseminen ja RED-menetelmä
  4. Kontekstin välitys AsyncLocalStoragella
← Takaisin: Node.js-taustakehityksen bootcamp