Pipeline OTEL
Questa pagina documenta la pipeline di log OpenTelemetry condivisa da playroll-cpp e playroll-ui: come ciascun servizio invia i log, quali attributi vi associa e su quali campi è possibile filtrare o raggruppare in modo sicuro nelle query SigNoz.
I due servizi puntano allo stesso endpoint OTLP/HTTP JSON e serializzano attributi di risorsa con forma identica, in modo che le dashboard possano raggruppare per utente, host o canale di rilascio senza diramazioni per servizio. Gli schemi sono intenzionalmente allineati — quando i due divergono, la fonte di verità è playroll-cpp/src/util/logger.cpp::sendLogsToOtel e playroll-ui la segue.
Endpoint e trasporto
| Proprietà | Valore |
|---|---|
| Endpoint | https://otel.playroll.gg/v1/logs |
| Protocollo | OTLP/HTTP JSON (una POST per batch, Content-Type: application/json) |
| Autenticazione | Nessuna — il ricevitore accetta POST non autenticate dai client. |
playroll-cpp decodifica l'endpoint a runtime da una stringa offuscata in XOR in include/service/endpoint_config.hpp (config::otelEndpoint()). playroll-ui lo risolve dalla variabile d'ambiente PLAYROLL_OTEL_ENDPOINT e ricade sullo stesso default in src/main/telemetry/otel-logger.ts (normalizeOtelEndpoint). Il normalizzatore della UI aggiunge /v1/logs se il path manca, replicando il comportamento di parseEndpoint in cpp.
Schema degli attributi di risorsa (allineato tra i servizi)
Gli attributi di risorsa compaiono una sola volta per voce resourceLogs e descrivono l'emittente. SigNoz li indicizza come resources_string['<key>'] e sono il punto giusto su cui filtrare quando vuoi "tutti i log da questo utente / host / canale" senza pagare il costo per record.
| Chiave | Tipo | Sorgente — playroll-cpp | Sorgente — playroll-ui | Note |
|---|---|---|---|---|
service.name | string | setServiceInfo("playroll-cpp", ...) in service_logger_config.cpp | costante letterale "playroll-ui" in src/main/index.ts | L'unico valore intenzionalmente divergente tra i due servizi. Usalo per separare le query per servizio. |
service.version | string | costante projectVersion passata a setServiceInfo (CMake PLAYROLL_VERSION) | app.getVersion() (versione del package Electron, fallback a "0.0.0") | Versione dell'applicazione. |
host.name | string | getHostname() (Winsock gethostname) | os.hostname() (Node) | Hostname della macchina Windows. |
process.pid | int | GetCurrentProcessId() | process.pid | PID OS del processo emittente. |
host.ownerId | string | ownerId da setOwnerCredentials (ID utente Supabase) | userContext.ownerId da setUserContext (ID utente Supabase) | Sempre presente, anche prima del login — emesso come stringa vuota "" finché non arrivano le credenziali, quindi le query su host.ownerId = '' sono valide per filtrare il "pre-login". |
app.channel | string | releaseTrack da setReleaseMetadata, canonicalizzato a test | dev | stable | resolveReleaseTrack(PLAYROLL_RELEASE_TRACK), stessa canonicalizzazione | Track di rilascio. Qualunque valore al di fuori dei tre canonici viene forzato a stable. Emesso solo se non vuoto. |
app.installer_version | string | variabile d'ambiente PLAYROLL_INSTALLER_VERSION, fallback a projectVersion | variabile d'ambiente PLAYROLL_INSTALLER_VERSION, fallback a app.getVersion() | Versione marcata dall'installer. Permette alle dashboard di distinguere "installato tramite installer 3.1.2" da "binario 3.1.2 sostituito a caldo". Emesso solo se non vuoto. |
host.username | string | username da setOwnerCredentials (username Steam, o ID utente Supabase se l'username Steam non è disponibile) | userContext.username da setUserContext (username Steam) | Condizionale — allegato solo dopo l'arrivo del contesto utente. |
host.PlayrollUsername | string | uguale a host.username | uguale a host.username | Alias retrocompatibile usato dalle vecchie dashboard di analytics. Sempre emesso insieme a host.username. |
enduser.id | string | ownerId, emesso come attributo separato accanto a host.ownerId | idem | Chiave semantica standard OTEL enduser.id. Uguale a host.ownerId quando presente; assente quando l'utente non è loggato. |
Storicamente playroll-ui emetteva anche service.instance.id, deployment.environment.name, os.type e os.release a livello di risorsa. Questi sono stati rimossi nel passaggio di allineamento al cpp (commit UI e60f3cd, 2026-05-15) in modo che la forma della risorsa coincida esattamente con playroll-cpp — non costruire query su questi campi, non compariranno nei nuovi payload.
Come vengono popolati i campi ricorrenti
I campi su cui filtrerai più spesso provengono da un piccolo numero di code path. L'intento è documentato qui in modo che le dashboard possano ragionare sui buchi (per esempio: perché enduser.id manca sugli eventi early-boot).
host.ownerId ed enduser.id
- cpp: impostati da
util::Logger::setOwnerCredentials(steamUsername, userId). Chiamati daauth_session_restore.cpp(ripristino del token al boot dalTokenStore) e daauth_http_callbacks.cpp(completamento di un OAuth fresco).userIdè l'ID utente Supabase. Finché questo non scatta,ownerIdè vuoto e la risorsa portahost.ownerId = ""senza alcun attributoenduser.id. - ui: impostati da
OtelLogger.setUserContext({ ownerId, username })damain/index.tsnel ramoauth.state_changed → authenticated.ownerIdèauthManager.getCurrentUser().id(ID utente Supabase). Resettati daclearUserContext()sulogged_out.
Entrambi i servizi trattengono i flush iniziali fino a 120 s in attesa delle credenziali, in modo che il primo batch parta con enduser.id valorizzato:
- cpp:
startOtelThreadchiamaotelCv.wait_for(lock, 120s, [] { return credentialsSet.load(); })prima di avviare il ciclo di export. - ui:
OtelLogger.flushInternalesce subito finché!credentialsSet && Date.now() < credentialsGraceDeadlineMs(default 120 s).shutdown()aggira il gate in modo che gli eventi in coda partano comunque alla chiusura dell'app.
Se l'utente non si autentica entro la finestra di grazia, gli eventi partono senza enduser.id — filtra su host.ownerId = '' per trovarli.
host.username e host.PlayrollUsername
- cpp: argomento
usernamedisetOwnerCredentials. Il chiamante passa l'username Steam quando disponibile, altrimenti ricade suuserIdin modo che il campo non sia mai vuoto post-login. - ui: campo
usernamediOtelUserContext, preso daauthManager.getCurrentUser().steamUsername. Può essere assente se l'utente si è autenticato senza un collegamento Steam.
host.PlayrollUsername è sempre emesso come duplicato letterale di host.username per compatibilità con le dashboard precedenti alla chiave standard.
app.channel
Entrambi i servizi canonicalizzano a uno tra test, dev, stable. Qualunque altra cosa (incluso il valore mancante) viene forzata a stable. Trattali come release track, non come deployment environment — il letterale production è volutamente assente dal vocabolario perché la pipeline OTEL veicola traffico di produzione indipendentemente dal valore del canale.
- cpp:
service_logger_config.cpp::canonicalReleaseTracklegge la variabile d'ambientePLAYROLL_RELEASE_TRACK, fallback aldefaultReleaseTrackbuild-time passato dall'entry point. - ui:
src/shared/release-track.ts::resolveReleaseTracklegge la stessa variabile d'ambiente, fallback aCOMPILE_TIME_DEFAULT(attualmente"test").
L'installer imposta PLAYROLL_RELEASE_TRACK prima di lanciare entrambi i processi, in modo che i due binari vedano lo stesso canale senza ricompilare.
app.installer_version
Impostato dalla variabile d'ambiente PLAYROLL_INSTALLER_VERSION in entrambi i servizi. Quando la versione marcata dall'installer diverge da service.version hai un binario sostituito a caldo (build di sviluppo che rimpiazza un binario installato, o auto-update in volo). Quando la variabile non è impostata, entrambi i servizi ricadono su service.version in modo che il campo non manchi mai post-deployment.
Schema degli attributi di record
Gli attributi di record vivono su ciascun logRecord e descrivono l'evento, non l'emittente. Usali per filtrare per tipo di evento, severità o payload del singolo evento — non metterci mai l'identità dell'utente.
| Chiave | Tipo | Emesso da | Note |
|---|---|---|---|
log.level | string | entrambi | Sempre presente. Specchia severityText. Valori: DEBUG, INFO, WARN, ERROR, FATAL. |
log.record_id | string | solo ui | UUIDv4 univoco per record di log. Utile per fare join dello stesso record tra log e trace. |
session.id | string | solo ui | ID di sessione per login (rigenerato su clearUserContext). Permette di raggruppare in SigNoz tutti gli eventi di una stessa sessione utente. |
source.file / source.line | string / int | solo cpp | __FILE__ / __LINE__ del call site LOG_*. La UI non emette posizione sorgente; usa il body e event.name. |
thread.id / thread.name | int / string | solo cpp | ID del thread OS e label del thread registrata (es. RemoteLogger, CaptureV2). La UI è single-threaded ai fini della telemetria. |
event.name | string | entrambi | ID evento normalizzato in snake-dotted (es. auth.state_changed, capture.v2.start). Lato UI, OtelLogger.event(level, eventName, ...) normalizza il nome e deriva anche event.domain. |
event.domain | string | ui | Primo segmento puntato di event.name, usato come chiave di raggruppamento grossolana in SigNoz. |
payload.<key> | string / int / double / bool | ui (installConsoleBridge) | Estratti automaticamente dai campi primitivi degli oggetti passati a console.log/info/warn/error/debug. I valori non primitivi vengono ignorati per mantenere lo schema del record piatto. |
log.source | string | ui (installConsoleBridge) | Identifica le chiamate console intercettate dal bridge: console.log, console.warn, ecc. |
exception.type / exception.message / exception.stacktrace | string | ui | Estratti automaticamente quando un'istanza Error viene passata a console.*. Solo il primo Error per call site viene catturato. |
Il catalogo completo dei valori di event.name provenienti dalla pipeline di registrazione (cpp) è documentato in Catalogo Eventi di Telemetria e Segnali principali. Gli eventi emessi dalla UI sono ad-hoc — cerca otelLogger.event(...) in playroll-ui/src/main/ per enumerarli.
Attributi di scope
Ogni batch ha un solo scope con due campi. Non vengono filtrati di frequente ma identificano quale istanza di logger ha emesso il batch.
| Chiave | Valore playroll-cpp | Valore playroll-ui |
|---|---|---|
scope.name | playroll-logger | playroll-ui-main |
scope.version | 1.0.0 (hardcoded) | specchia service.version |
Batching, gate e affidabilità
| Comportamento | playroll-cpp | playroll-ui |
|---|---|---|
| Trigger del batch | Il buffer raggiunge 30 record OPPURE scatta il timer pushInterval (default 5 s, configurabile via setPushInterval). | Il buffer raggiunge maxBatchSize (default 50, env PLAYROLL_OTEL_BATCH_SIZE) OPPURE scatta il flush timer (default 10 s, env PLAYROLL_OTEL_FLUSH_MS). |
| Gate pre-credenziali | Il thread worker attende fino a 120 s su una condvar per credentialsSet. | flushInternal non fa nulla finché credentialsSet non è vero o scade la grazia (default 120 s, configurabile via credentialsGraceMs). |
| Drain a shutdown | stopOtelThread svuota sincronamente il buffer rimanente. | shutdown() azzera il gate delle credenziali e fa un'ultima flush() con timeout di 2 s. |
| Retry su errore HTTP | Il batch fallito viene rimesso in testa alla coda; il tick successivo riprova. | Il batch fallito viene rimesso in testa alla coda; il flush successivo riprova. Emette diagnostiche otel.export_reject / otel.export_fail con throttle 1 / 10 s. |
| Overflow | Limitato dal buffer; cpp non scarta. | Scarta i record più vecchi oltre maxQueueSize (default 2000, env PLAYROLL_OTEL_MAX_QUEUE_SIZE); emette otel.queue_overflow con throttle 1 / 10 s. |
Pattern di query utili (SigNoz)
Filtra a un utente specifico su entrambi i servizi:
resources_string['host.ownerId'] = '<supabase-user-id>'
Filtra a uno specifico canale di rilascio:
resources_string['app.channel'] = 'stable'
Filtra agli eventi solo-UI o solo-cpp:
resources_string['service.name'] = 'playroll-ui'
resources_string['service.name'] = 'playroll-cpp'
Trova gli eventi UI pre-login (la finestra di grazia delle credenziali è scaduta senza utente):
resources_string['service.name'] = 'playroll-ui'
AND resources_string['host.ownerId'] = ''
Trova i binari sostituiti a caldo (la versione marcata dall'installer è diversa dalla versione in esecuzione):
resources_string['app.installer_version'] != resources_string['service.version']
Riferimenti al codice
| Ambito | playroll-cpp | playroll-ui |
|---|---|---|
| Risoluzione dell'endpoint | include/service/endpoint_config.hpp::otelEndpoint | src/main/telemetry/otel-logger.ts::normalizeOtelEndpoint |
| Setup del logger | src/service/service_logger_config.cpp::configureServiceLogger | src/main/index.ts (costruzione di OtelLogger) |
| Costruzione degli attributi di risorsa | src/util/logger.cpp::sendLogsToOtel | src/main/telemetry/otel-logger.ts::buildResourceAttributes |
| Cablaggio delle credenziali utente | src/service/auth_session_restore.cpp, src/service/auth_http_callbacks.cpp (chiamano setOwnerCredentials) | src/main/index.ts (chiama otelLogger.setUserContext su auth.state_changed) |
| Canonicalizzazione del release track | src/service/service_logger_config.cpp::canonicalReleaseTrack | src/shared/release-track.ts::canonicalizeReleaseTrack |
Manutenzione della documentazione
Se cambi la lista degli attributi di risorsa in uno dei due servizi, aggiorna questa pagina nello stesso change in modo che il contratto cross-service resti esplicito. L'aggiunta di nuovi attributi a livello di record non richiede un aggiornamento della doc a meno che siano pensati per essere filtrabili dalle dashboard.