Rakenteinen lokitus palvelin- ja Edge-ympäristöissä
Tuottakaa korreloituja, rakenteisia lokeja, jotka säilyvät serverless-ympäristön kylmäkäynnistyksissä ja Edge-rajoituksissa.
Rakenteinen lokitus palvelin- ja Edge-ympäristöissä on ilmainen Next.js 15 -fullstack-kehitys (App Router + Server Actions)-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Next.js 15 -fullstack-kehitys (App Router + Server Actions)-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Next.js 15 -fullstack-kehitys (App Router + Server Actions)-kurssilla on yhteensä 4 oppituntia.
Miksi jäsennelty lokitus on tärkeää Next.js 15:ssä
Perinteinen console.log tuottaa jäsentämättömiä merkkijonoja. Tuotannossa toimivissa Next.js 15 -sovelluksissa, jotka käyttävät serverless-funktioita ja Edge-ajoympäristöjä, nämä merkkijonot ovat lähes hyödyttömiä: pyyntöjen välillä ei ole korrelaatiota, kenttiä ei voi jäsentää koneellisesti, ja kylmäkäynnistykset hävittävät puskuroidun tulosteen ennen sen lähettämistä.
Jäsennelty lokitus ratkaisee ongelman tuottamalla JSON-objekteja, joissa on yhdenmukaiset kentät jokaisella lokirivillä:
- requestId — yhdistää jokaisen lokin yhteen HTTP-pyyntöön
- timestamp — ISO-8601-muotoinen, aina UTC-aikavyöhykkeellä
- level —
info/warn/error - service — mikä reitti tai funktio tuotti lokin
- message — ihmisen luettava yhteenveto
- context — mielivaltainen jäsennelty hyötykuorma
Observability-alustat (Datadog, Axiom, Grafana Loki) käsittelevät nämä kentät automaattisesti, joten voit suodattaa ja yhdistää tietoja tuhansien samanaikaisten pyyntöjen välillä sekunneissa.
Lokituksen ydinrajapinta
Määrittele ennen mihinkään ajoympäristöön integroitumista minimaalinen ja siirrettävä logger-rajapinta. Näin liiketoimintalogiikka pysyy irrotettuna konkreettisesta lokitustoteutuksesta ja testaaminen on helppoa.
Luo tiedosto lib/logger/types.ts määrittämään muoto, jota jokaisen loggerin on noudatettava:
// lib/logger/types.ts
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export interface LogContext {
requestId?: string;
userId?: string;
route?: string;
durationMs?: number;
[key: string]: unknown;
}
export interface Logger {
debug(message: string, context?: LogContext): void;
info(message: string, context?: LogContext): void;
warn(message: string, context?: LogContext): void;
error(message: string, context?: LogContext & { err?: unknown }): void;
}
export interface LogEntry {
timestamp: string;
level: LogLevel;
service: string;
message: string;
context: LogContext;
}JSON-loggerin rakentaminen Node.js-ajoympäristöön
Next.js 15:n Server Components -komponentit, Route Handlerit ja Server Actions -toiminnot suoritetaan oletusarvoisesti Node.js-ajoympäristössä. Siellä käytettävissäsi on process.stdout, ja voit tuottaa monirivistä JSONia turvallisesti.
Keskeinen toimintaperiaate on kirjoittaa yksi JSON-objekti riville (NDJSON- / JSON Lines -muoto). Lokien koontipalvelut jakavat sisällön rivinvaihtojen kohdalta, joten monirivinen tuloste rikkoo tietojen vastaanoton.
Huomaa, että err sarjallistetaan manuaalisesti — JSON.stringify jättää hiljaisesti pois Error-ominaisuudet, kuten stack ja message.
// lib/logger/node-logger.ts
import type { Logger, LogContext, LogEntry, LogLevel } from './types';
function serializeError(err: unknown): Record<string, unknown> {
if (err instanceof Error) {
return { name: err.name, message: err.message, stack: err.stack };
}
return { raw: String(err) };
}
function createEntry(
level: LogLevel,
service: string,
message: string,
context: LogContext = {}
): LogEntry {
return {
timestamp: new Date().toISOString(),
level,
service,
message,
context,
};
}
export function createNodeLogger(service: string): Logger {
const write = (entry: LogEntry) =>
process.stdout.write(JSON.stringify(entry) + '\n');
return {
debug: (msg, ctx) => write(createEntry('debug', service, msg, ctx)),
info: (msg, ctx) => write(createEntry('info', service, msg, ctx)),
warn: (msg, ctx) => write(createEntry('warn', service, msg, ctx)),
error: (msg, ctx) => {
const { err, ...rest } = ctx ?? {};
write(createEntry('error', service, msg, {
...rest,
...(err !== undefined ? { error: serializeError(err) } : {}),
}));
},
};
}Edge-yhteensopiva logger: rajoitusten hallinta
Edge Runtime (jota käyttävät Middleware sekä Route Handlerit, joissa on export const runtime = 'edge') poistaa käytöstä suurimman osan Node.js:n rajapinnoista. Et voi käyttää process.stdout.write- tai fs-rajapintaa.
Voit käyttää seuraavia:
console.log/console.error— aina käytettävissäFetch API— lokien välittämiseen ulkoiseen päätepisteeseencrypto.randomUUID()— pyyntötunnisteita varten
Ratkaisu on tuottaa edelleen JSON-merkkijonoja console.log-kutsulla. Vercel Edgen lokit puskuroidaan riveittäin ja välitetään lokien vastaanottopalveluun tavallisena tekstinä, joten yksi JSON-objekti rivillä toimii turvallisesti.
// lib/logger/edge-logger.ts
import type { Logger, LogContext, LogEntry, LogLevel } from './types';
function createEntry(
level: LogLevel,
service: string,
message: string,
context: LogContext = {}
): LogEntry {
return {
timestamp: new Date().toISOString(),
level,
service,
message,
context,
};
}
export function createEdgeLogger(service: string): Logger {
const emit = (entry: LogEntry) => {
// console.log is the only safe output channel in Edge Runtime
const line = JSON.stringify(entry);
if (entry.level === 'error') {
console.error(line);
} else {
console.log(line);
}
};
return {
debug: (msg, ctx) => emit(createEntry('debug', service, msg, ctx)),
info: (msg, ctx) => emit(createEntry('info', service, msg, ctx)),
warn: (msg, ctx) => emit(createEntry('warn', service, msg, ctx)),
error: (msg, ctx) => emit(createEntry('error', service, msg, ctx)),
};
}Pyyntöjen yhdistäminen AsyncLocalStorage-säilön avulla
Serverless-lokituksen vaikein ongelma on korrelaatio: saman requestId-tunnisteen liittäminen jokaiseen yhden pyynnön aikana tuotettuun lokiin, myös syvällä apufunktioissa, ilman että tunniste välitetään parametrina kaikkialle.
Node.js:n AsyncLocalStorage ratkaisee tämän. Se luo pyyntökohtaisen kontekstisäilön, joka kulkee automaattisesti asynkronisten ketjujen — await-kutsujen, Promise.then-kutsujen ja ajastimien — läpi ilman erillistä välitystä.
Next.js 15 tukee tätä suoraan Node.js-ajoympäristössä. Luo säilömoduuli:
// lib/logger/request-store.ts
import { AsyncLocalStorage } from 'async_hooks';
export interface RequestStore {
requestId: string;
userId?: string;
startTime: number;
}
// One singleton for the process lifetime
export const requestStore = new AsyncLocalStorage<RequestStore>();
export function getRequestContext(): Partial<RequestStore> {
return requestStore.getStore() ?? {};
}
// Wrap any async work in this to bind a store
export function runWithRequestContext<T>(
store: RequestStore,
fn: () => Promise<T>
): Promise<T> {
return requestStore.run(store, fn);
}Kontekstin huomioiva logger säilön avulla
Liitä nyt AsyncLocalStorage-säilö loggeriin. Jokainen lokikutsu lukee automaattisesti requestId- ja userId-arvot aktiivisesta kontekstista — props-arvojen välittäminen syvälle komponenttihierarkiaan ei ole tarpeen.
Päivitä tiedosto lib/logger/node-logger.ts yhdistämään ympäröivän kontekstisäilön tiedot ennen kirjoittamista:
// lib/logger/context-logger.ts
import { createNodeLogger } from './node-logger';
import { getRequestContext } from './request-store';
import type { Logger, LogContext } from './types';
export function createContextLogger(service: string): Logger {
const base = createNodeLogger(service);
function enrich(ctx: LogContext = {}): LogContext {
const { requestId, userId, startTime } = getRequestContext();
return {
...(requestId ? { requestId } : {}),
...(userId ? { userId } : {}),
...(startTime
? { elapsedMs: Date.now() - startTime }
: {}),
...ctx, // caller can override ambient values if needed
};
}
return {
debug: (msg, ctx) => base.debug(msg, enrich(ctx)),
info: (msg, ctx) => base.info(msg, enrich(ctx)),
warn: (msg, ctx) => base.warn(msg, enrich(ctx)),
error: (msg, ctx) => base.error(msg, enrich(ctx)),
};
}
// Singleton used everywhere in Node.js routes
export const logger = createContextLogger('nextjs-app');Kontekstin lisääminen Middleware-ohjelmassa
Ihanteellinen paikka määrittää requestId on Next.js Middleware, joka käsittelee jokaisen pyynnön ennen reititystä. Voit luoda tunnisteen, liittää sen pyynnön otsakkeeseen (x-request-id) ja välittää sen eteenpäin sekä Server Components -komponenteille että API-reiteille.
Huomaa: Middleware suoritetaan Edge Runtime -ympäristössä. Käytä crypto.randomUUID()-funktiota (älä uuid-pakettia) ja createEdgeLogger-funktiota.
// middleware.ts (project root)
import { NextRequest, NextResponse } from 'next/server';
import { createEdgeLogger } from '@/lib/logger/edge-logger';
const log = createEdgeLogger('middleware');
export function middleware(req: NextRequest) {
// Honour an upstream gateway's ID if present
const requestId =
req.headers.get('x-request-id') ?? crypto.randomUUID();
const start = Date.now();
log.info('request started', {
requestId,
method: req.method,
path: req.nextUrl.pathname,
});
const response = NextResponse.next();
// Forward the ID so Route Handlers and Server Actions can read it
response.headers.set('x-request-id', requestId);
log.info('request forwarded', {
requestId,
durationMs: Date.now() - start,
});
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};AsyncLocalStoragen alustaminen Route Handlerissa
Lue Node.js:n Route Handlerissa x-request-id-otsake (jonka Middleware asetti) ja alusta AsyncLocalStorage-säilö. Kaikki runWithRequestContext-funktion sisällä kutsuttu — myös sisäkkäiset await-kutsut ja apufunktiot — perii tämän kontekstin automaattisesti.
// app/api/orders/route.ts
import { type NextRequest, NextResponse } from 'next/server';
import { runWithRequestContext } from '@/lib/logger/request-store';
import { logger } from '@/lib/logger/context-logger';
import { fetchOrders } from '@/lib/orders';
export async function GET(req: NextRequest) {
const requestId =
req.headers.get('x-request-id') ?? crypto.randomUUID();
return runWithRequestContext(
{ requestId, startTime: Date.now() },
async () => {
logger.info('fetching orders');
// up arrow automatically includes requestId + elapsedMs
try {
const orders = await fetchOrders();
logger.info('orders fetched', { count: orders.length });
return NextResponse.json(orders);
} catch (err) {
logger.error('failed to fetch orders', { err });
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
}
}
);
}Lokitus Server Actions -toiminnoissa
Next.js 15:n Server Actions -toiminnot suoritetaan palvelimella Node.js-ajoympäristössä, joten AsyncLocalStorage toimii myös tässä. Mukana on kuitenkin hienovarainen rajoitus: toiminnon kutsuu React-renderöijä, ei suora HTTP-käsittelijä, joten säilö on alustettava itse toiminnon alussa.
Yleinen malli on kääre-apufunktio, joka alustaa säilön ja tarjoaa loggerin, jolloin kunkin toiminnon runko pysyy selkeänä:
// lib/actions/with-logging.ts
import { headers } from 'next/headers';
import { runWithRequestContext } from '@/lib/logger/request-store';
import { logger } from '@/lib/logger/context-logger';
type ActionFn<TArgs extends unknown[], TResult> =
(...args: TArgs) => Promise<TResult>;
export function withLogging<TArgs extends unknown[], TResult>(
name: string,
fn: ActionFn<TArgs, TResult>
): ActionFn<TArgs, TResult> {
return async (...args) => {
const headersList = await headers();
const requestId =
headersList.get('x-request-id') ?? crypto.randomUUID();
return runWithRequestContext(
{ requestId, startTime: Date.now() },
async () => {
logger.info('action started: ' + name, { action: name });
try {
const result = await fn(...args);
logger.info('action completed: ' + name);
return result;
} catch (err) {
logger.error('action failed: ' + name, { err });
throw err;
}
}
);
};
}
// Usage in a Server Action:
// export const submitOrder = withLogging('submitOrder', async (data) => { ... });Näytteenotto ja lokitasot tuotannossa
Paljon liikennettä käsittelevässä Next.js-sovelluksessa jokaisen debug-lauseen kirjaaminen on kallista sekä laskenta-ajan että tietojen vastaanottokustannusten kannalta. Käytä yhdessä kahta tekniikkaa:
- Lokitason rajaaminen: tarkista
process.env.LOG_LEVELja ohita kynnysarvon alapuolella olevat tasot. Tuotannossa käytetään yleensä tasoainfo; debug otetaan käyttöön vain häiriötilanteissa. - Näytteenotto: meluisilla
info-poluilla (kuten terveystarkistuksissa) kirjaa vain prosenttiosuus pyynnöistä, jotta määrää voidaan vähentää menettämättä kaikkea tietoa.
Erillinen esimerkki näytteenoton ja lokitason rajaamisen ydintoiminnasta:
// Standalone demo — runs without any framework
const LEVELS = ['debug', 'info', 'warn', 'error'];
const MIN_LEVEL = process.env['LOG_LEVEL'] || 'info';
function shouldLog(level) {
return LEVELS.indexOf(level) >= LEVELS.indexOf(MIN_LEVEL);
}
function sample(rate) {
// rate = 0.1 means log 10% of the time
return Math.random() < rate;
}
function log(level, message, sampleRate = 1) {
if (!shouldLog(level)) return;
if (!sample(sampleRate)) return;
console.log(JSON.stringify({ level, message, ts: new Date().toISOString() }));
}
// Simulated high-frequency health-check path — logs only ~10%
for (let i = 0; i < 20; i++) {
log('info', '/api/health called', 0.1);
}
// Errors always log regardless of sample rate
log('error', 'Database connection failed');Lokien välittäminen ulkoiseen vastaanottopalveluun
Serverless-funktiot ovat lyhytikäisiä — stdout on luotettava vain, jos alustasi tallentaa sen (Vercel tekee niin, paljas AWS Lambda ei oletusarvoisesti). Lokien välityspalvelu / etävastaanottopalvelu (Axiom, Better Stack, Datadog) takaa tietojen säilyvyyden.
Edge Runtime -ympäristöstä voit välittää lokit fetch-kutsulla ja käyttää waitUntil-toimintoa, jolloin HTTP-vastaus palautetaan heti ja lokin POST-pyyntö suoritetaan taustalla. Node.js:n Route Handlereissa käytä samaan estottomaan toimintaan after()-funktiota (Next.js 15).
// lib/logger/axiom-drain.ts
// Edge-compatible log drain using fetch
const AXIOM_DATASET = process.env['AXIOM_DATASET'] ?? '';
const AXIOM_TOKEN = process.env['AXIOM_API_TOKEN'] ?? '';
export async function sendToAxiom(
entries: object[]
): Promise<void> {
if (!AXIOM_DATASET || !AXIOM_TOKEN) return; // skip in dev
await fetch(
'https://api.axiom.co/v1/datasets/' + AXIOM_DATASET + '/ingest',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-ndjson',
Authorization: 'Bearer ' + AXIOM_TOKEN,
},
// NDJSON: one JSON object per line
body: entries.map((e) => JSON.stringify(e)).join('\n'),
}
);
}
// In a Middleware or Edge Route Handler:
// context.waitUntil(sendToAxiom([entry]));
//
// In a Node.js Route Handler (Next.js 15):
// import { after } from 'next/server';
// after(() => sendToAxiom([entry]));Pikatarkistus: korrelaatio ajoympäristöjen välillä
Jokaisella yksittäisen käyttäjän pyynnön lokirivillä — joka kulkee Softwaren Middleware-vaiheen (Edge), Route Handlerin (Node.js) ja sisäkkäisen Server Actions -toiminnon kautta — on oltava sama requestId. Mikä lähestymistapa on oikea?
Oppitunnin yhteenveto: jäsennelty lokitus palvelimella ja Edgessä
Tässä oppitunnissa rakensitte täydellisen tuotantovalmiin jäsennellyn lokituksen putken Next.js 15:lle:
- Siirrettävä rajapinta (
Logger,LogContext) pitää liiketoimintalogiikan irrotettuna lokituksen taustajärjestelmästä. - Ajoympäristökohtaiset toteutukset:
createNodeLoggerkirjoittaa NDJSON-muotoa kohteeseenprocess.stdout;createEdgeLoggerkäyttääconsole.log-kutsua, joka on Edge Runtime -ympäristön ainoa turvallinen tulostuskanava. - Korrelaatio
AsyncLocalStorage-toiminnolla: alusta säilö kerran (Route Handlerissa tai Server Actions -toiminnon kääreessä), jolloin jokainen sisäkkäinenawaitperii automaattisesti arvotrequestId,userIdja kuluneen ajan. - Middleware pyyntötunnisteen lähteenä: luo Middleware-ohjelmassa uusi
x-request-id-otsake tai hyväksy ylemmältä tasolta saatu otsake, välitä se eteenpäin ja alusta Node.js-säilö vastaanotettaessa. - Tuotannon toimintaperiaatteet: rajaa lokitasoja
LOG_LEVEL-ympäristömuuttujalla, käytä näytteenottoa meluisilla poluilla, sarjallistaError-objektit eksplisiittisesti ja välitä lokit pysyvään ulkoiseen vastaanottopalveluunwaitUntil- taiafter()-toiminnolla, jotta vastaukset eivät esty.
Nämä mallit yhdessä tarjoavat korreloitua, koneellisesti jäsennettävää observability-tietoa, joka kestää kylmäkäynnistykset, samanaikaiset pyynnöt sekä Node.js- ja Edge-ajoympäristöjen rajoitukset.
Opi TypeScript 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
- 88
Usein kysytyt kysymykset
Onko oppitunti ”Rakenteinen lokitus palvelin- ja Edge-ympäristöissä” ilmainen?
Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Next.js 15 -fullstack-kehitys (App Router + Server Actions)-oppimispolun 3 oppituntia, myös oppitunnin “Rakenteinen lokitus palvelin- ja Edge-ympäristöissä”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Next.js 15 -fullstack-kehitys (App Router + Server Actions)-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Rakenteinen lokitus palvelin- ja Edge-ympäristöissä”?
Tuottakaa korreloituja, rakenteisia lokeja, jotka säilyvät serverless-ympäristön kylmäkäynnistyksissä ja Edge-rajoituksissa. Harjoittelet Next.js 15 -fullstack-kehitys (App Router + Server Actions)-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Next.js 15 -fullstack-kehitys (App Router + Server Actions)-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Next.js 15 -fullstack-kehitys (App Router + Server Actions)-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.
Kuinka kauan ”Rakenteinen lokitus palvelin- ja Edge-ympäristöissä”-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ä Next.js 15 -fullstack-kehitys (App Router + Server Actions)-oppitunnilla?
Kyllä. Jokainen Next.js 15 -fullstack-kehitys (App Router + Server Actions)-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
- OpenTelemetry-jäljitys instrumentation.ts-tiedostolla
- Hienojakoiset error.tsx- ja global-error-rajat
- Rakenteinen lokitus palvelin- ja Edge-ympäristöissä
- Server Action -virheiden ja telemetrian kerääminen