agent serveur
Node
Le seul agent qui instrumente vos propres fonctions sans qu’on le lui demande : une transformation au build ouvre un span par fonction asynchrone exportée, si bien qu’une requête a un appelant à qui être attribuée.
- paquet
@sixty-sh/nodesur npm- tourne sur
- Node 20 ou plus récent, n’importe quel processus serveur que vous déployez.
- source
- sixty-sh/sixty-node
L’installer
A server process you control: functions, HTTP routes and SQL.
L’installation est écrite comme un prompt pour l’agent de code que vous avez déjà ouvert, pas comme une liste de tâches pour vous. C’est délibéré : elle nomme ce qui doit être vrai une fois l’installation terminée plutôt que les fichiers à modifier, parce que l’endroit où va le code dépend du framework et que le mettre au mauvais endroit échoue silencieusement. Un agent peut lire votre dépôt et le déduire ; un paragraphe sur une page de documentation, non.
Le même texte est ce que renvoie install_sixty via le serveur MCP et ce que le collecteur sert à /v1/setup?kind=node. Il n’en existe qu’une seule copie.
l’installation Node, en entier
Install the @sixty-sh/node agent in this service so its functions, HTTP routes
and database queries report to sixty.
Before editing, inspect whether this service makes LLM or agent calls. If it
does, ask the user: "Do you want LLM monitoring?" Do not enable it without
their answer. Core HTTP, function and database monitoring is installed either
way. If they say yes, also follow the AI wiring in step 3; if no, leave every
AI client and call site untouched.
1. Install @sixty-sh/node as a dependency (not a devDependency — it runs in
production; that is the entire point).
2. Call init() from "@sixty-sh/node" in the server entry point, BEFORE the
application imports anything else. It patches the database client at call
time, so a module that already imported "pg", "mysql2", "postgres" or
"mongodb" above it is not instrumented. In an ESM entry, imports are
hoisted above statements — so either put the init in its own module
imported first, or use --import as in step 4.
Then check which database client this project actually uses. pg, mysql2,
postgres.js and mongodb are found and patched by init() with nothing
further to do — including Mongoose, which drives the mongodb driver.
PRISMA IS THE EXCEPTION and it is the one worth checking for, because
getting it wrong is invisible. Prisma does not use pg — it runs queries
through its own engine, so no patch reaches them — and its extension hook
returns a NEW client instead of modifying the one it was given. So the
application has to use the wrapped client:
import { instrumentPrisma } from "@sixty-sh/node"
export const prisma = instrumentPrisma(new PrismaClient())
Replace the project's existing PrismaClient export with that one. Skip this
and everything still reports except the queries, which is the failure that
looks like success.
3. If LLM monitoring was requested, instrument only the SDKs the project
actually uses: instrumentOpenAI(client), instrumentAnthropic(client),
instrumentAISDK({ generateText, streamText }), or langChainCallbacks().
For other providers and OpenAI-compatible services such as DeepSeek, wrap
the call with generation({ provider, model }, () => call()). Never attach
prompts, generated text, tool arguments or tool results; sixty records
timing, provider/model, token usage and cost metadata only.
4. Turn on function-level tracing. Work out which of these this project can
use; do not do both:
a. It has a build step (Next, Vite, webpack, Rollup, esbuild). Wrap the
config with withSixty() from "@sixty-sh/node/transform", or add the
matching bundler plugin from that same module. The transform needs
@babel/core and unplugin, which are peer dependencies — without them it
emits nothing and says nothing, so add them if they are not there.
b. It has no build step — it is started with a plain "node src/server.js"
or "tsx src/server.ts". Add --import @sixty-sh/node/register to the
start command, which both installs the transform and calls init() early
enough that step 2 is already satisfied.
NEXT.JS, SPECIFICALLY. Use (a) for the transform, and start init() from a
flag rather than from instrumentation.js:
// sixty.mjs, beside package.json
import { init } from "@sixty-sh/node"
init()
# the deployed start command
node --import ./sixty.mjs node_modules/next/dist/bin/next start
instrumentation.js looks like the right hook and is compiled once per
runtime. If the app has middleware there is an edge compilation of it, and
the bundler pulls the agent's node:fs, node:http and node:crypto into a
bundle that cannot read those schemes — the build fails. A NEXT_RUNTIME
guard inside register() does not help: it runs at runtime, and the
bundling already happened.
Change the command the deployed process actually runs, not only the local
dev script. A Dockerfile CMD, a Procfile, or a platform start command
overrides package.json and is the one that matters.
5. Set these environment variables wherever the service is deployed:
SIXTY_API_KEY = a secret key starting sixty_sk_ — ask me for it. Do not
invent one, and do not commit it.
SIXTY_SERVICE = my-app
SIXTY_ENDPOINT = https://ingest.sixty.sh
The release identifier is picked up automatically on Vercel, Render,
Railway, Fly, Heroku and GitHub Actions. If this deploys some other way,
set SIXTY_RELEASE to the commit SHA — without one, every measurement lands
in a single nameless bucket and no comparison can ever be made.
Constraints — correctness requirements, not style preferences:
- Do NOT change any application behaviour. This is instrumentation only: no
refactors, no reordering of business logic, no "while I was in here" fixes.
- Do NOT apply the transform to client bundles. It is for server code; in a
framework that builds both, restrict it to the server build.
- Do NOT add any analytics library, user id, session id, or cookie to what is
reported. The agent is deliberately anonymous and must stay that way.
- If a hot function must not be traced, put // @sixty-ignore above it rather
than turning the transform off for the whole file.
When you are done, tell me which files you changed and what the deployed start
command now is, so I can confirm data is arriving.Il lui faut une clé secrète — elle commence par sixty_sk_ et reste côté serveur. Générez-en une sur la page Réglages une fois connecté.
Ce qu’il mesure
| signal | unité | ce que cela veut dire |
|---|---|---|
rows | rows per call | this query returns more rows than it used to |
fanout | queries per call | this operation now issues more database calls per invocation — an N+1 |
latency | ms per call | this operation takes longer end to end than it used to |
self_latency | ms per call | the time spent in this function itself got longer — its children did not |
payload | bytes per call | the serialized result of this operation got bigger |
errors | error rate | a larger fraction of calls are throwing |
runaway | calls per minute | this operation is being called far more often than anything triggers it |
repeated_query | times per request | the identical query runs several times within one request |
overfetch | rows per call | far more rows are fetched than the code appears to use |
unbounded | rows per call | this query has no upper bound on what it can return |
recursion | levels deep | this operation calls itself, deeper than it should |
new_error | occurrences | an error that did not occur in the previous release |
missing_tenancy | — | This reads a table of per-person data without saying whose rows it wants. Unless your database is filtering it for you, everyone gets everyone else's. |
collapse | — | This is handing back roughly half the data it used to, or less. If that was not deliberate, something is filtering out rows that somebody expects to see. |
vanished | — | It was being used steadily until this release and has not been used once since. Usually the link, button, or redirect that led here stopped working. |
traffic_drop | — | This is still being used, but a fraction as often, and its share of your traffic fell too — so it is not just a quiet period. |
round_trips | round trips per read | one read now waits on the database many times instead of once |
plan | — | the database chose a different plan for this query |
Où il s’accroche
- N’importe quel serveur node:http — Express, Fastify, Koa, Hono, les route handlers de Next.js — c’est le module serveur qui est patché, pas le framework, donc rien n’a besoin d’adaptateur.
- Vos propres fonctions — Une transformation au build enveloppe les fonctions asynchrones exportées. C’est ce qui transforme « le point d’entrée a ralenti » en « cette fonction s’est mise à émettre quatorze requêtes ».
Bases de données
- pg — Lignes, forme de la requête, et plans de requête via EXPLAIN.
- mysql2 — Lignes et lignes affectées, à la fois pour query et execute, résultats en flux compris.
- postgres.js — Les tagged templates ne mettent jamais de valeurs dans la requête : il n’y a donc rien à caviarder.
- mongodb — La forme de la commande comme identité, les documents comme lignes, et les allers-retours du curseur.
- Prisma — Explicite : instrumentPrisma(new PrismaClient()).
Ce que seul celui-ci fait
- Des spans de fonction automatiques — Pas de décorateur, pas d’include, pas deux lignes en tête de la fonction. Tous les autres agents serveur vous demandent de marquer le code qui mérite d’être mesuré.
- Des plans de requête sur Postgres — Un EXPLAIN à plan générique, mis en cache par requête, exécuté en dehors du span de l’appelant pour ne jamais être mesuré comme son travail.
- Les allers-retours de curseur — Des lectures MongoDB qui arrivent par versements — un signal qu’aucun autre pilote n’expose.
Ce qu’il ne peut pas faire
- Les spans concurrents sont distingués par AsyncLocalStorage, que le pool de connexions met en défaut par défaut. Les adaptateurs rattachent les callbacks du pool au contexte qui les a créés ; un pilote que nous n’instrumentons pas attribuera ses requêtes à qui a libéré une connexion.
- Le CPU et l’attente ne peuvent pas être séparés. process.cpuUsage() concerne tout le processus et beaucoup de contextes asynchrones s’entrelacent sur un seul thread : aucun span ne peut honnêtement en revendiquer une part.
Configuration
Chaque agent lit les mêmes quatre variables, et DRIFT_* répond toujours partout où SIXTY_* répond — le produit a été renommé, et ce nom ne nous appartient pas au point de le retirer des déploiements des autres.
SIXTY_API_KEY | Sans elle l’agent reste inerte et le dit. Il ne devine jamais, ne réessaie jamais contre un point d’entrée inconnu, et ne lève jamais d’exception. |
|---|---|
SIXTY_SERVICE | Comment appeler ce service. Par défaut le nom du projet là où il est lisible. |
SIXTY_RELEASE | La plus importante. Récupérée automatiquement sur Vercel, Render, Railway, Fly, Heroku et GitHub Actions ; partout ailleurs, mettez-y le SHA du commit. Sans elle, chaque mesure atterrit dans un unique seau sans nom et aucune comparaison n’est jamais possible. |
SIXTY_ENDPOINT | Où remonter. Par défaut http://localhost:4319, ce qui est juste sur un portable et faux dès l’instant où l’application est servie à quelqu’un d’autre. |
Le reste — intervalle d’envoi, taux d’échantillonnage, quoi instrumenter — est dans le README du paquet lui-même, là où il peut rester vrai à mesure que l’agent change.