[{"data":1,"prerenderedAt":3526},["ShallowReactive",2],{"content:\u002F11-observability\u002F03-opentelemetry-tracing-go":3},{"title":4,"description":5,"path":6,"body":7},"Tracing в Go: OpenTelemetry и propagation","Trace показывает путь одной операции через сервис, БД, cache, broker и внешние API. Он особенно полезен, когда latency набирается не в одном месте, а по цепочке.","\u002F11-observability\u002F03-opentelemetry-tracing-go",{"type":8,"value":9,"toc":3510},"minimark",[10,14,17,20,31,38,43,49,52,84,87,110,113,117,120,123,203,213,217,1066,1079,1082,1085,1141,1144,1187,1194,1354,1357,1361,1364,1641,1644,1648,1651,1654,1720,1723,1804,1811,1814,1821,1901,1911,1915,1921,1924,1942,1945,1956,1959,2030,2033,2113,2116,2483,2492,2497,2500,2517,2520,2524,2537,2540,2601,2604,2610,2614,2619,2621,2657,2735,2739,2749,2892,2895,2901,2905,2908,2931,2934,2937,2951,2954,3002,3009,3013,3016,3030,3033,3037,3040,3083,3087,3120,3123,3152,3155,3159,3189,3340,3506],[11,12,4],"h1",{"id":13},"tracing-в-go-opentelemetry-и-propagation",[15,16,5],"p",{},[15,18,19],{},"OpenTelemetry не стоит объяснять как \"библиотеку для трейсов\". Это стандартный контракт telemetry:",[21,22,28],"pre",{"className":23,"code":25,"language":26,"meta":27},[24],"language-text","Go service\n  -> OpenTelemetry SDK \u002F instrumentation\n  -> OTLP\n  -> OpenTelemetry Collector\n  -> Tempo, Jaeger, Prometheus, Loki, Elastic or vendor\n","text","",[29,30,25],"code",{"__ignoreMap":27},[15,32,33,34,37],{},"На 6 мая 2026 в OpenTelemetry Go статус такой: traces stable, metrics stable, logs beta. Поэтому в учебном проекте основной путь: traces и metrics через OTel SDK, logs через ",[29,35,36],{},"slog"," JSON + collector\u002Fagent, а OTel logs показываем как advanced-тему.",[39,40,42],"h2",{"id":41},"trace-span-attributes","Trace, span, attributes",[21,44,47],{"className":45,"code":46,"language":26,"meta":27},[24],"trace: convert request\n  span: HTTP POST \u002Fconvert\n    span: usecase.Convert\n      span: postgres GetLatestRate\n      span: redis Set cache\n      span: kafka PublishConversionEvent\n",[29,48,46],{"__ignoreMap":27},[15,50,51],{},"Trace - вся цепочка. Span - один участок работы. У span есть:",[53,54,55,63,66,69,72,75,78,81],"ul",{},[56,57,58,59,62],"li",{},"name: ",[29,60,61],{},"usecase.Convert",";",[56,64,65],{},"kind: server, client, internal, producer, consumer;",[56,67,68],{},"start\u002Fend time;",[56,70,71],{},"attributes;",[56,73,74],{},"events;",[56,76,77],{},"links;",[56,79,80],{},"status;",[56,82,83],{},"recorded errors.",[15,85,86],{},"Хорошие span boundaries:",[53,88,89,92,95,98,101,104,107],{},[56,90,91],{},"входящий HTTP\u002FgRPC request;",[56,93,94],{},"use case;",[56,96,97],{},"repository call;",[56,99,100],{},"external API call;",[56,102,103],{},"Redis operation;",[56,105,106],{},"Kafka publish\u002Fconsume;",[56,108,109],{},"background job.",[15,111,112],{},"Не надо создавать span на каждую мелкую функцию. Trace должен быть диагностической картой, а не дампом call stack.",[39,114,116],{"id":115},"resource-attributes","Resource attributes",[15,118,119],{},"Resource отвечает на вопрос: \"кто породил telemetry\".",[15,121,122],{},"Обязательные поля:",[124,125,126,140],"table",{},[127,128,129],"thead",{},[130,131,132,137],"tr",{},[133,134,136],"th",{"align":135},"left","Attribute",[133,138,139],{"align":135},"Пример",[141,142,143,156,168,181,191],"tbody",{},[130,144,145,151],{},[146,147,148],"td",{"align":135},[29,149,150],{},"service.name",[146,152,153],{"align":135},[29,154,155],{},"ratedesk-api",[130,157,158,163],{},[146,159,160],{"align":135},[29,161,162],{},"service.namespace",[146,164,165],{"align":135},[29,166,167],{},"mentor",[130,169,170,175],{},[146,171,172],{"align":135},[29,173,174],{},"service.version",[146,176,177,180],{"align":135},[29,178,179],{},"1.12.4"," или git SHA",[130,182,183,188],{},[146,184,185],{"align":135},[29,186,187],{},"service.instance.id",[146,189,190],{"align":135},"pod\u002Fcontainer id",[130,192,193,198],{},[146,194,195],{"align":135},[29,196,197],{},"deployment.environment.name",[146,199,200],{"align":135},[29,201,202],{},"production",[15,204,205,206,208,209,212],{},"Если не задать ",[29,207,150],{},", в Grafana\u002FTempo\u002FJaeger все быстро превращается в ",[29,210,211],{},"unknown_service",".",[39,214,216],{"id":215},"инициализация-sdk","Инициализация SDK",[21,218,223],{"className":219,"code":220,"language":221,"meta":222,"style":27},"language-go shiki shiki-themes github-dark","package telemetry\n\nimport (\n    \"context\"\n    \"errors\"\n    \"time\"\n\n    \"go.opentelemetry.io\u002Fotel\"\n    \"go.opentelemetry.io\u002Fotel\u002Fattribute\"\n    \"go.opentelemetry.io\u002Fotel\u002Fexporters\u002Fotlp\u002Fotlpmetric\u002Fotlpmetricgrpc\"\n    \"go.opentelemetry.io\u002Fotel\u002Fexporters\u002Fotlp\u002Fotlptrace\u002Fotlptracegrpc\"\n    \"go.opentelemetry.io\u002Fotel\u002Fpropagation\"\n    \"go.opentelemetry.io\u002Fotel\u002Fsdk\u002Fmetric\"\n    \"go.opentelemetry.io\u002Fotel\u002Fsdk\u002Fresource\"\n    \"go.opentelemetry.io\u002Fotel\u002Fsdk\u002Ftrace\"\n    semconv \"go.opentelemetry.io\u002Fotel\u002Fsemconv\u002Fv1.40.0\"\n    \"google.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure\"\n)\n\nfunc Init(ctx context.Context, service, env, version string) (func(context.Context) error, error) {\n    res, err := resource.Merge(resource.Default(), resource.NewWithAttributes(\n        semconv.SchemaURL,\n        semconv.ServiceName(service),\n        semconv.ServiceNamespace(\"mentor\"),\n        semconv.ServiceVersion(version),\n        attribute.String(\"deployment.environment.name\", env),\n    ))\n    if err != nil {\n        return nil, err\n    }\n\n    traceExp, err := otlptracegrpc.New(ctx,\n        otlptracegrpc.WithEndpoint(\"otel-collector:4317\"),\n        otlptracegrpc.WithTLSCredentials(insecure.NewCredentials()),\n    )\n    if err != nil {\n        return nil, err\n    }\n\n    tp := trace.NewTracerProvider(\n        trace.WithResource(res),\n        trace.WithSampler(trace.ParentBased(trace.TraceIDRatioBased(0.10))),\n        trace.WithBatcher(traceExp),\n    )\n    otel.SetTracerProvider(tp)\n\n    metricExp, err := otlpmetricgrpc.New(ctx,\n        otlpmetricgrpc.WithEndpoint(\"otel-collector:4317\"),\n        otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials()),\n    )\n    if err != nil {\n        return nil, err\n    }\n\n    mp := metric.NewMeterProvider(\n        metric.WithResource(res),\n        metric.WithReader(metric.NewPeriodicReader(metricExp, metric.WithInterval(15*time.Second))),\n    )\n    otel.SetMeterProvider(mp)\n\n    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(\n        propagation.TraceContext{},\n        propagation.Baggage{},\n    ))\n\n    return func(ctx context.Context) error {\n        return errors.Join(tp.Shutdown(ctx), mp.Shutdown(ctx))\n    }, nil\n}\n","go","no-run",[29,224,225,238,245,255,268,278,288,293,303,313,323,333,343,353,363,373,387,397,403,408,477,507,513,525,541,552,569,575,594,605,611,616,633,649,666,672,685,694,699,704,720,732,759,770,775,787,792,807,821,834,839,852,861,866,871,887,897,928,933,944,949,965,979,991,996,1001,1026,1051,1060],{"__ignoreMap":27},[226,227,230,234],"span",{"class":228,"line":229},"line",1,[226,231,233],{"class":232},"snl16","package",[226,235,237],{"class":236},"svObZ"," telemetry\n",[226,239,241],{"class":228,"line":240},2,[226,242,244],{"emptyLinePlaceholder":243},true,"\n",[226,246,248,251],{"class":228,"line":247},3,[226,249,250],{"class":232},"import",[226,252,254],{"class":253},"s95oV"," (\n",[226,256,258,262,265],{"class":228,"line":257},4,[226,259,261],{"class":260},"sU2Wk","    \"",[226,263,264],{"class":236},"context",[226,266,267],{"class":260},"\"\n",[226,269,271,273,276],{"class":228,"line":270},5,[226,272,261],{"class":260},[226,274,275],{"class":236},"errors",[226,277,267],{"class":260},[226,279,281,283,286],{"class":228,"line":280},6,[226,282,261],{"class":260},[226,284,285],{"class":236},"time",[226,287,267],{"class":260},[226,289,291],{"class":228,"line":290},7,[226,292,244],{"emptyLinePlaceholder":243},[226,294,296,298,301],{"class":228,"line":295},8,[226,297,261],{"class":260},[226,299,300],{"class":236},"go.opentelemetry.io\u002Fotel",[226,302,267],{"class":260},[226,304,306,308,311],{"class":228,"line":305},9,[226,307,261],{"class":260},[226,309,310],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fattribute",[226,312,267],{"class":260},[226,314,316,318,321],{"class":228,"line":315},10,[226,317,261],{"class":260},[226,319,320],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fexporters\u002Fotlp\u002Fotlpmetric\u002Fotlpmetricgrpc",[226,322,267],{"class":260},[226,324,326,328,331],{"class":228,"line":325},11,[226,327,261],{"class":260},[226,329,330],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fexporters\u002Fotlp\u002Fotlptrace\u002Fotlptracegrpc",[226,332,267],{"class":260},[226,334,336,338,341],{"class":228,"line":335},12,[226,337,261],{"class":260},[226,339,340],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fpropagation",[226,342,267],{"class":260},[226,344,346,348,351],{"class":228,"line":345},13,[226,347,261],{"class":260},[226,349,350],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fsdk\u002Fmetric",[226,352,267],{"class":260},[226,354,356,358,361],{"class":228,"line":355},14,[226,357,261],{"class":260},[226,359,360],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fsdk\u002Fresource",[226,362,267],{"class":260},[226,364,366,368,371],{"class":228,"line":365},15,[226,367,261],{"class":260},[226,369,370],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fsdk\u002Ftrace",[226,372,267],{"class":260},[226,374,376,379,382,385],{"class":228,"line":375},16,[226,377,378],{"class":253},"    semconv ",[226,380,381],{"class":260},"\"",[226,383,384],{"class":236},"go.opentelemetry.io\u002Fotel\u002Fsemconv\u002Fv1.40.0",[226,386,267],{"class":260},[226,388,390,392,395],{"class":228,"line":389},17,[226,391,261],{"class":260},[226,393,394],{"class":236},"google.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure",[226,396,267],{"class":260},[226,398,400],{"class":228,"line":399},18,[226,401,402],{"class":253},")\n",[226,404,406],{"class":228,"line":405},19,[226,407,244],{"emptyLinePlaceholder":243},[226,409,411,414,417,420,424,427,429,432,435,438,440,443,445,448,451,454,456,458,460,462,464,467,470,472,474],{"class":228,"line":410},20,[226,412,413],{"class":232},"func",[226,415,416],{"class":236}," Init",[226,418,419],{"class":253},"(",[226,421,423],{"class":422},"s9osk","ctx",[226,425,426],{"class":236}," context",[226,428,212],{"class":253},[226,430,431],{"class":236},"Context",[226,433,434],{"class":253},", ",[226,436,437],{"class":422},"service",[226,439,434],{"class":253},[226,441,442],{"class":422},"env",[226,444,434],{"class":253},[226,446,447],{"class":422},"version",[226,449,450],{"class":232}," string",[226,452,453],{"class":253},") (",[226,455,413],{"class":232},[226,457,419],{"class":253},[226,459,264],{"class":236},[226,461,212],{"class":253},[226,463,431],{"class":236},[226,465,466],{"class":253},") ",[226,468,469],{"class":232},"error",[226,471,434],{"class":253},[226,473,469],{"class":232},[226,475,476],{"class":253},") {\n",[226,478,480,483,486,489,492,495,498,501,504],{"class":228,"line":479},21,[226,481,482],{"class":253},"    res, err ",[226,484,485],{"class":232},":=",[226,487,488],{"class":253}," resource.",[226,490,491],{"class":236},"Merge",[226,493,494],{"class":253},"(resource.",[226,496,497],{"class":236},"Default",[226,499,500],{"class":253},"(), resource.",[226,502,503],{"class":236},"NewWithAttributes",[226,505,506],{"class":253},"(\n",[226,508,510],{"class":228,"line":509},22,[226,511,512],{"class":253},"        semconv.SchemaURL,\n",[226,514,516,519,522],{"class":228,"line":515},23,[226,517,518],{"class":253},"        semconv.",[226,520,521],{"class":236},"ServiceName",[226,523,524],{"class":253},"(service),\n",[226,526,528,530,533,535,538],{"class":228,"line":527},24,[226,529,518],{"class":253},[226,531,532],{"class":236},"ServiceNamespace",[226,534,419],{"class":253},[226,536,537],{"class":260},"\"mentor\"",[226,539,540],{"class":253},"),\n",[226,542,544,546,549],{"class":228,"line":543},25,[226,545,518],{"class":253},[226,547,548],{"class":236},"ServiceVersion",[226,550,551],{"class":253},"(version),\n",[226,553,555,558,561,563,566],{"class":228,"line":554},26,[226,556,557],{"class":253},"        attribute.",[226,559,560],{"class":236},"String",[226,562,419],{"class":253},[226,564,565],{"class":260},"\"deployment.environment.name\"",[226,567,568],{"class":253},", env),\n",[226,570,572],{"class":228,"line":571},27,[226,573,574],{"class":253},"    ))\n",[226,576,578,581,584,587,591],{"class":228,"line":577},28,[226,579,580],{"class":232},"    if",[226,582,583],{"class":253}," err ",[226,585,586],{"class":232},"!=",[226,588,590],{"class":589},"sDLfK"," nil",[226,592,593],{"class":253}," {\n",[226,595,597,600,602],{"class":228,"line":596},29,[226,598,599],{"class":232},"        return",[226,601,590],{"class":589},[226,603,604],{"class":253},", err\n",[226,606,608],{"class":228,"line":607},30,[226,609,610],{"class":253},"    }\n",[226,612,614],{"class":228,"line":613},31,[226,615,244],{"emptyLinePlaceholder":243},[226,617,619,622,624,627,630],{"class":228,"line":618},32,[226,620,621],{"class":253},"    traceExp, err ",[226,623,485],{"class":232},[226,625,626],{"class":253}," otlptracegrpc.",[226,628,629],{"class":236},"New",[226,631,632],{"class":253},"(ctx,\n",[226,634,636,639,642,644,647],{"class":228,"line":635},33,[226,637,638],{"class":253},"        otlptracegrpc.",[226,640,641],{"class":236},"WithEndpoint",[226,643,419],{"class":253},[226,645,646],{"class":260},"\"otel-collector:4317\"",[226,648,540],{"class":253},[226,650,652,654,657,660,663],{"class":228,"line":651},34,[226,653,638],{"class":253},[226,655,656],{"class":236},"WithTLSCredentials",[226,658,659],{"class":253},"(insecure.",[226,661,662],{"class":236},"NewCredentials",[226,664,665],{"class":253},"()),\n",[226,667,669],{"class":228,"line":668},35,[226,670,671],{"class":253},"    )\n",[226,673,675,677,679,681,683],{"class":228,"line":674},36,[226,676,580],{"class":232},[226,678,583],{"class":253},[226,680,586],{"class":232},[226,682,590],{"class":589},[226,684,593],{"class":253},[226,686,688,690,692],{"class":228,"line":687},37,[226,689,599],{"class":232},[226,691,590],{"class":589},[226,693,604],{"class":253},[226,695,697],{"class":228,"line":696},38,[226,698,610],{"class":253},[226,700,702],{"class":228,"line":701},39,[226,703,244],{"emptyLinePlaceholder":243},[226,705,707,710,712,715,718],{"class":228,"line":706},40,[226,708,709],{"class":253},"    tp ",[226,711,485],{"class":232},[226,713,714],{"class":253}," trace.",[226,716,717],{"class":236},"NewTracerProvider",[226,719,506],{"class":253},[226,721,723,726,729],{"class":228,"line":722},41,[226,724,725],{"class":253},"        trace.",[226,727,728],{"class":236},"WithResource",[226,730,731],{"class":253},"(res),\n",[226,733,735,737,740,743,746,748,751,753,756],{"class":228,"line":734},42,[226,736,725],{"class":253},[226,738,739],{"class":236},"WithSampler",[226,741,742],{"class":253},"(trace.",[226,744,745],{"class":236},"ParentBased",[226,747,742],{"class":253},[226,749,750],{"class":236},"TraceIDRatioBased",[226,752,419],{"class":253},[226,754,755],{"class":589},"0.10",[226,757,758],{"class":253},"))),\n",[226,760,762,764,767],{"class":228,"line":761},43,[226,763,725],{"class":253},[226,765,766],{"class":236},"WithBatcher",[226,768,769],{"class":253},"(traceExp),\n",[226,771,773],{"class":228,"line":772},44,[226,774,671],{"class":253},[226,776,778,781,784],{"class":228,"line":777},45,[226,779,780],{"class":253},"    otel.",[226,782,783],{"class":236},"SetTracerProvider",[226,785,786],{"class":253},"(tp)\n",[226,788,790],{"class":228,"line":789},46,[226,791,244],{"emptyLinePlaceholder":243},[226,793,795,798,800,803,805],{"class":228,"line":794},47,[226,796,797],{"class":253},"    metricExp, err ",[226,799,485],{"class":232},[226,801,802],{"class":253}," otlpmetricgrpc.",[226,804,629],{"class":236},[226,806,632],{"class":253},[226,808,810,813,815,817,819],{"class":228,"line":809},48,[226,811,812],{"class":253},"        otlpmetricgrpc.",[226,814,641],{"class":236},[226,816,419],{"class":253},[226,818,646],{"class":260},[226,820,540],{"class":253},[226,822,824,826,828,830,832],{"class":228,"line":823},49,[226,825,812],{"class":253},[226,827,656],{"class":236},[226,829,659],{"class":253},[226,831,662],{"class":236},[226,833,665],{"class":253},[226,835,837],{"class":228,"line":836},50,[226,838,671],{"class":253},[226,840,842,844,846,848,850],{"class":228,"line":841},51,[226,843,580],{"class":232},[226,845,583],{"class":253},[226,847,586],{"class":232},[226,849,590],{"class":589},[226,851,593],{"class":253},[226,853,855,857,859],{"class":228,"line":854},52,[226,856,599],{"class":232},[226,858,590],{"class":589},[226,860,604],{"class":253},[226,862,864],{"class":228,"line":863},53,[226,865,610],{"class":253},[226,867,869],{"class":228,"line":868},54,[226,870,244],{"emptyLinePlaceholder":243},[226,872,874,877,879,882,885],{"class":228,"line":873},55,[226,875,876],{"class":253},"    mp ",[226,878,485],{"class":232},[226,880,881],{"class":253}," metric.",[226,883,884],{"class":236},"NewMeterProvider",[226,886,506],{"class":253},[226,888,890,893,895],{"class":228,"line":889},56,[226,891,892],{"class":253},"        metric.",[226,894,728],{"class":236},[226,896,731],{"class":253},[226,898,900,902,905,908,911,914,917,919,922,925],{"class":228,"line":899},57,[226,901,892],{"class":253},[226,903,904],{"class":236},"WithReader",[226,906,907],{"class":253},"(metric.",[226,909,910],{"class":236},"NewPeriodicReader",[226,912,913],{"class":253},"(metricExp, metric.",[226,915,916],{"class":236},"WithInterval",[226,918,419],{"class":253},[226,920,921],{"class":589},"15",[226,923,924],{"class":232},"*",[226,926,927],{"class":253},"time.Second))),\n",[226,929,931],{"class":228,"line":930},58,[226,932,671],{"class":253},[226,934,936,938,941],{"class":228,"line":935},59,[226,937,780],{"class":253},[226,939,940],{"class":236},"SetMeterProvider",[226,942,943],{"class":253},"(mp)\n",[226,945,947],{"class":228,"line":946},60,[226,948,244],{"emptyLinePlaceholder":243},[226,950,952,954,957,960,963],{"class":228,"line":951},61,[226,953,780],{"class":253},[226,955,956],{"class":236},"SetTextMapPropagator",[226,958,959],{"class":253},"(propagation.",[226,961,962],{"class":236},"NewCompositeTextMapPropagator",[226,964,506],{"class":253},[226,966,968,971,973,976],{"class":228,"line":967},62,[226,969,970],{"class":236},"        propagation",[226,972,212],{"class":253},[226,974,975],{"class":236},"TraceContext",[226,977,978],{"class":253},"{},\n",[226,980,982,984,986,989],{"class":228,"line":981},63,[226,983,970],{"class":236},[226,985,212],{"class":253},[226,987,988],{"class":236},"Baggage",[226,990,978],{"class":253},[226,992,994],{"class":228,"line":993},64,[226,995,574],{"class":253},[226,997,999],{"class":228,"line":998},65,[226,1000,244],{"emptyLinePlaceholder":243},[226,1002,1004,1007,1010,1012,1014,1016,1018,1020,1022,1024],{"class":228,"line":1003},66,[226,1005,1006],{"class":232},"    return",[226,1008,1009],{"class":232}," func",[226,1011,419],{"class":253},[226,1013,423],{"class":422},[226,1015,426],{"class":236},[226,1017,212],{"class":253},[226,1019,431],{"class":236},[226,1021,466],{"class":253},[226,1023,469],{"class":232},[226,1025,593],{"class":253},[226,1027,1029,1031,1034,1037,1040,1043,1046,1048],{"class":228,"line":1028},67,[226,1030,599],{"class":232},[226,1032,1033],{"class":253}," errors.",[226,1035,1036],{"class":236},"Join",[226,1038,1039],{"class":253},"(tp.",[226,1041,1042],{"class":236},"Shutdown",[226,1044,1045],{"class":253},"(ctx), mp.",[226,1047,1042],{"class":236},[226,1049,1050],{"class":253},"(ctx))\n",[226,1052,1054,1057],{"class":228,"line":1053},68,[226,1055,1056],{"class":253},"    }, ",[226,1058,1059],{"class":589},"nil\n",[226,1061,1063],{"class":228,"line":1062},69,[226,1064,1065],{"class":253},"}\n",[15,1067,1068,1069,1072,1073,1075,1076,212],{},"Версию ",[29,1070,1071],{},"semconv"," пиньте под используемый ",[29,1074,300],{}," module. В примерах курса версия фиксирована ради повторяемости, но в реальном проекте проверяйте актуальную совместимую версию в ",[29,1077,1078],{},"pkg.go.dev",[15,1080,1081],{},"В local\u002Fstaging можно временно ставить 100% sampling. В production обычно начинают с head sampling, а дальше переходят к tail sampling в Collector для медленных\u002Fошибочных traces.",[15,1083,1084],{},"Endpoint лучше брать из env, а не хардкодить в коде:",[21,1086,1088],{"className":219,"code":1087,"language":221,"meta":222,"style":27},"endpoint := os.Getenv(\"OTEL_EXPORTER_OTLP_ENDPOINT\")\nif endpoint == \"\" {\n    endpoint = \"otel-collector:4317\"\n}\n",[29,1089,1090,1110,1126,1137],{"__ignoreMap":27},[226,1091,1092,1095,1097,1100,1103,1105,1108],{"class":228,"line":229},[226,1093,1094],{"class":253},"endpoint ",[226,1096,485],{"class":232},[226,1098,1099],{"class":253}," os.",[226,1101,1102],{"class":236},"Getenv",[226,1104,419],{"class":253},[226,1106,1107],{"class":260},"\"OTEL_EXPORTER_OTLP_ENDPOINT\"",[226,1109,402],{"class":253},[226,1111,1112,1115,1118,1121,1124],{"class":228,"line":240},[226,1113,1114],{"class":232},"if",[226,1116,1117],{"class":253}," endpoint ",[226,1119,1120],{"class":232},"==",[226,1122,1123],{"class":260}," \"\"",[226,1125,593],{"class":253},[226,1127,1128,1131,1134],{"class":228,"line":247},[226,1129,1130],{"class":253},"    endpoint ",[226,1132,1133],{"class":232},"=",[226,1135,1136],{"class":260}," \"otel-collector:4317\"\n",[226,1138,1139],{"class":228,"line":257},[226,1140,1065],{"class":253},[15,1142,1143],{},"Порты:",[124,1145,1146,1159],{},[127,1147,1148],{},[130,1149,1150,1153,1156],{},[133,1151,1152],{"align":135},"Endpoint",[133,1154,1155],{"align":135},"Протокол",[133,1157,1158],{"align":135},"Когда использовать",[141,1160,1161,1174],{},[130,1162,1163,1168,1171],{},[146,1164,1165],{"align":135},[29,1166,1167],{},"4317",[146,1169,1170],{"align":135},"OTLP\u002FgRPC",[146,1172,1173],{"align":135},"Частый production default для SDK -> Collector.",[130,1175,1176,1181,1184],{},[146,1177,1178],{"align":135},[29,1179,1180],{},"4318",[146,1182,1183],{"align":135},"OTLP\u002FHTTP",[146,1185,1186],{"align":135},"Удобен для proxies, serverless, curl-like diagnostics.",[15,1188,1189,1190,1193],{},"В ",[29,1191,1192],{},"main"," shutdown telemetry должен быть частью graceful shutdown:",[21,1195,1197],{"className":219,"code":1196,"language":221,"meta":222,"style":27},"shutdownTelemetry, err := telemetry.Init(ctx, cfg.ServiceName, cfg.Env, cfg.Version)\nif err != nil {\n    return fmt.Errorf(\"init telemetry: %w\", err)\n}\ndefer func() {\n    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n    defer cancel()\n    if err := shutdownTelemetry(ctx); err != nil {\n        logger.Error(\"telemetry shutdown failed\", \"error\", err)\n    }\n}()\n",[29,1198,1199,1215,1227,1250,1254,1264,1294,1305,1325,1345,1349],{"__ignoreMap":27},[226,1200,1201,1204,1206,1209,1212],{"class":228,"line":229},[226,1202,1203],{"class":253},"shutdownTelemetry, err ",[226,1205,485],{"class":232},[226,1207,1208],{"class":253}," telemetry.",[226,1210,1211],{"class":236},"Init",[226,1213,1214],{"class":253},"(ctx, cfg.ServiceName, cfg.Env, cfg.Version)\n",[226,1216,1217,1219,1221,1223,1225],{"class":228,"line":240},[226,1218,1114],{"class":232},[226,1220,583],{"class":253},[226,1222,586],{"class":232},[226,1224,590],{"class":589},[226,1226,593],{"class":253},[226,1228,1229,1231,1234,1237,1239,1242,1245,1247],{"class":228,"line":247},[226,1230,1006],{"class":232},[226,1232,1233],{"class":253}," fmt.",[226,1235,1236],{"class":236},"Errorf",[226,1238,419],{"class":253},[226,1240,1241],{"class":260},"\"init telemetry: ",[226,1243,1244],{"class":589},"%w",[226,1246,381],{"class":260},[226,1248,1249],{"class":253},", err)\n",[226,1251,1252],{"class":228,"line":257},[226,1253,1065],{"class":253},[226,1255,1256,1259,1261],{"class":228,"line":270},[226,1257,1258],{"class":232},"defer",[226,1260,1009],{"class":232},[226,1262,1263],{"class":253},"() {\n",[226,1265,1266,1269,1271,1274,1277,1280,1283,1286,1289,1291],{"class":228,"line":280},[226,1267,1268],{"class":253},"    ctx, cancel ",[226,1270,485],{"class":232},[226,1272,1273],{"class":253}," context.",[226,1275,1276],{"class":236},"WithTimeout",[226,1278,1279],{"class":253},"(context.",[226,1281,1282],{"class":236},"Background",[226,1284,1285],{"class":253},"(), ",[226,1287,1288],{"class":589},"10",[226,1290,924],{"class":232},[226,1292,1293],{"class":253},"time.Second)\n",[226,1295,1296,1299,1302],{"class":228,"line":290},[226,1297,1298],{"class":232},"    defer",[226,1300,1301],{"class":236}," cancel",[226,1303,1304],{"class":253},"()\n",[226,1306,1307,1309,1311,1313,1316,1319,1321,1323],{"class":228,"line":295},[226,1308,580],{"class":232},[226,1310,583],{"class":253},[226,1312,485],{"class":232},[226,1314,1315],{"class":236}," shutdownTelemetry",[226,1317,1318],{"class":253},"(ctx); err ",[226,1320,586],{"class":232},[226,1322,590],{"class":589},[226,1324,593],{"class":253},[226,1326,1327,1330,1333,1335,1338,1340,1343],{"class":228,"line":305},[226,1328,1329],{"class":253},"        logger.",[226,1331,1332],{"class":236},"Error",[226,1334,419],{"class":253},[226,1336,1337],{"class":260},"\"telemetry shutdown failed\"",[226,1339,434],{"class":253},[226,1341,1342],{"class":260},"\"error\"",[226,1344,1249],{"class":253},[226,1346,1347],{"class":228,"line":315},[226,1348,610],{"class":253},[226,1350,1351],{"class":228,"line":325},[226,1352,1353],{"class":253},"}()\n",[15,1355,1356],{},"Если Collector временно недоступен, сервис не должен падать на каждом request. Ошибка exporter на старте - повод явно решить политику: в local можно fail fast, в production часто запускают сервис, но alertят на collector\u002Fexporter send failures.",[39,1358,1360],{"id":1359},"manual-instrumentation","Manual instrumentation",[15,1362,1363],{},"Manual instrumentation нужна там, где есть бизнес-смысл.",[21,1365,1367],{"className":219,"code":1366,"language":221,"meta":222,"style":27},"var tracer = otel.Tracer(\"ratedesk\u002Finternal\u002Fconversion\")\n\nfunc (s *Service) Convert(ctx context.Context, req ConvertRequest) (ConvertResult, error) {\n    ctx, span := tracer.Start(ctx, \"usecase.Convert\")\n    defer span.End()\n\n    span.SetAttributes(\n        attribute.String(\"currency.from\", req.From),\n        attribute.String(\"currency.to\", req.To),\n        attribute.String(\"rate.source\", req.Source),\n    )\n\n    result, err := s.converter.Convert(ctx, req)\n    if err != nil {\n        span.RecordError(err)\n        span.SetStatus(codes.Error, safeStatus(err))\n        return ConvertResult{}, err\n    }\n\n    span.SetStatus(codes.Ok, \"converted\")\n    return result, nil\n}\n",[29,1368,1369,1392,1396,1445,1466,1478,1482,1492,1506,1520,1534,1538,1542,1557,1569,1580,1596,1606,1610,1614,1628,1637],{"__ignoreMap":27},[226,1370,1371,1374,1377,1379,1382,1385,1387,1390],{"class":228,"line":229},[226,1372,1373],{"class":232},"var",[226,1375,1376],{"class":253}," tracer ",[226,1378,1133],{"class":232},[226,1380,1381],{"class":253}," otel.",[226,1383,1384],{"class":236},"Tracer",[226,1386,419],{"class":253},[226,1388,1389],{"class":260},"\"ratedesk\u002Finternal\u002Fconversion\"",[226,1391,402],{"class":253},[226,1393,1394],{"class":228,"line":240},[226,1395,244],{"emptyLinePlaceholder":243},[226,1397,1398,1400,1403,1406,1408,1411,1413,1416,1418,1420,1422,1424,1426,1428,1431,1434,1436,1439,1441,1443],{"class":228,"line":247},[226,1399,413],{"class":232},[226,1401,1402],{"class":253}," (",[226,1404,1405],{"class":422},"s ",[226,1407,924],{"class":232},[226,1409,1410],{"class":236},"Service",[226,1412,466],{"class":253},[226,1414,1415],{"class":236},"Convert",[226,1417,419],{"class":253},[226,1419,423],{"class":422},[226,1421,426],{"class":236},[226,1423,212],{"class":253},[226,1425,431],{"class":236},[226,1427,434],{"class":253},[226,1429,1430],{"class":422},"req",[226,1432,1433],{"class":236}," ConvertRequest",[226,1435,453],{"class":253},[226,1437,1438],{"class":236},"ConvertResult",[226,1440,434],{"class":253},[226,1442,469],{"class":232},[226,1444,476],{"class":253},[226,1446,1447,1450,1452,1455,1458,1461,1464],{"class":228,"line":257},[226,1448,1449],{"class":253},"    ctx, span ",[226,1451,485],{"class":232},[226,1453,1454],{"class":253}," tracer.",[226,1456,1457],{"class":236},"Start",[226,1459,1460],{"class":253},"(ctx, ",[226,1462,1463],{"class":260},"\"usecase.Convert\"",[226,1465,402],{"class":253},[226,1467,1468,1470,1473,1476],{"class":228,"line":270},[226,1469,1298],{"class":232},[226,1471,1472],{"class":253}," span.",[226,1474,1475],{"class":236},"End",[226,1477,1304],{"class":253},[226,1479,1480],{"class":228,"line":280},[226,1481,244],{"emptyLinePlaceholder":243},[226,1483,1484,1487,1490],{"class":228,"line":290},[226,1485,1486],{"class":253},"    span.",[226,1488,1489],{"class":236},"SetAttributes",[226,1491,506],{"class":253},[226,1493,1494,1496,1498,1500,1503],{"class":228,"line":295},[226,1495,557],{"class":253},[226,1497,560],{"class":236},[226,1499,419],{"class":253},[226,1501,1502],{"class":260},"\"currency.from\"",[226,1504,1505],{"class":253},", req.From),\n",[226,1507,1508,1510,1512,1514,1517],{"class":228,"line":305},[226,1509,557],{"class":253},[226,1511,560],{"class":236},[226,1513,419],{"class":253},[226,1515,1516],{"class":260},"\"currency.to\"",[226,1518,1519],{"class":253},", req.To),\n",[226,1521,1522,1524,1526,1528,1531],{"class":228,"line":315},[226,1523,557],{"class":253},[226,1525,560],{"class":236},[226,1527,419],{"class":253},[226,1529,1530],{"class":260},"\"rate.source\"",[226,1532,1533],{"class":253},", req.Source),\n",[226,1535,1536],{"class":228,"line":325},[226,1537,671],{"class":253},[226,1539,1540],{"class":228,"line":335},[226,1541,244],{"emptyLinePlaceholder":243},[226,1543,1544,1547,1549,1552,1554],{"class":228,"line":345},[226,1545,1546],{"class":253},"    result, err ",[226,1548,485],{"class":232},[226,1550,1551],{"class":253}," s.converter.",[226,1553,1415],{"class":236},[226,1555,1556],{"class":253},"(ctx, req)\n",[226,1558,1559,1561,1563,1565,1567],{"class":228,"line":355},[226,1560,580],{"class":232},[226,1562,583],{"class":253},[226,1564,586],{"class":232},[226,1566,590],{"class":589},[226,1568,593],{"class":253},[226,1570,1571,1574,1577],{"class":228,"line":365},[226,1572,1573],{"class":253},"        span.",[226,1575,1576],{"class":236},"RecordError",[226,1578,1579],{"class":253},"(err)\n",[226,1581,1582,1584,1587,1590,1593],{"class":228,"line":375},[226,1583,1573],{"class":253},[226,1585,1586],{"class":236},"SetStatus",[226,1588,1589],{"class":253},"(codes.Error, ",[226,1591,1592],{"class":236},"safeStatus",[226,1594,1595],{"class":253},"(err))\n",[226,1597,1598,1600,1603],{"class":228,"line":389},[226,1599,599],{"class":232},[226,1601,1602],{"class":236}," ConvertResult",[226,1604,1605],{"class":253},"{}, err\n",[226,1607,1608],{"class":228,"line":399},[226,1609,610],{"class":253},[226,1611,1612],{"class":228,"line":405},[226,1613,244],{"emptyLinePlaceholder":243},[226,1615,1616,1618,1620,1623,1626],{"class":228,"line":410},[226,1617,1486],{"class":253},[226,1619,1586],{"class":236},[226,1621,1622],{"class":253},"(codes.Ok, ",[226,1624,1625],{"class":260},"\"converted\"",[226,1627,402],{"class":253},[226,1629,1630,1632,1635],{"class":228,"line":479},[226,1631,1006],{"class":232},[226,1633,1634],{"class":253}," result, ",[226,1636,1059],{"class":589},[226,1638,1639],{"class":228,"line":509},[226,1640,1065],{"class":253},[15,1642,1643],{},"В attributes не кладите секреты, email, phone, request body и любые high-cardinality значения без явной причины.",[39,1645,1647],{"id":1646},"library-instrumentation","Library instrumentation",[15,1649,1650],{},"Для сетевых и инфраструктурных границ используйте готовую instrumentation.",[15,1652,1653],{},"HTTP server\u002Fclient:",[21,1655,1657],{"className":219,"code":1656,"language":221,"meta":222,"style":27},"handler := otelhttp.NewHandler(mux, \"ratedesk-api\")\n\nclient := &http.Client{\n    Transport: otelhttp.NewTransport(http.DefaultTransport),\n}\n",[29,1658,1659,1680,1684,1705,1716],{"__ignoreMap":27},[226,1660,1661,1664,1666,1669,1672,1675,1678],{"class":228,"line":229},[226,1662,1663],{"class":253},"handler ",[226,1665,485],{"class":232},[226,1667,1668],{"class":253}," otelhttp.",[226,1670,1671],{"class":236},"NewHandler",[226,1673,1674],{"class":253},"(mux, ",[226,1676,1677],{"class":260},"\"ratedesk-api\"",[226,1679,402],{"class":253},[226,1681,1682],{"class":228,"line":240},[226,1683,244],{"emptyLinePlaceholder":243},[226,1685,1686,1689,1691,1694,1697,1699,1702],{"class":228,"line":247},[226,1687,1688],{"class":253},"client ",[226,1690,485],{"class":232},[226,1692,1693],{"class":232}," &",[226,1695,1696],{"class":236},"http",[226,1698,212],{"class":253},[226,1700,1701],{"class":236},"Client",[226,1703,1704],{"class":253},"{\n",[226,1706,1707,1710,1713],{"class":228,"line":257},[226,1708,1709],{"class":253},"    Transport: otelhttp.",[226,1711,1712],{"class":236},"NewTransport",[226,1714,1715],{"class":253},"(http.DefaultTransport),\n",[226,1717,1718],{"class":228,"line":270},[226,1719,1065],{"class":253},[15,1721,1722],{},"gRPC:",[21,1724,1726],{"className":219,"code":1725,"language":221,"meta":222,"style":27},"srv := grpc.NewServer(\n    grpc.StatsHandler(otelgrpc.NewServerHandler()),\n)\n\nconn, err := grpc.NewClient(\n    addr,\n    grpc.WithStatsHandler(otelgrpc.NewClientHandler()),\n)\n",[29,1727,1728,1743,1759,1763,1767,1781,1786,1800],{"__ignoreMap":27},[226,1729,1730,1733,1735,1738,1741],{"class":228,"line":229},[226,1731,1732],{"class":253},"srv ",[226,1734,485],{"class":232},[226,1736,1737],{"class":253}," grpc.",[226,1739,1740],{"class":236},"NewServer",[226,1742,506],{"class":253},[226,1744,1745,1748,1751,1754,1757],{"class":228,"line":240},[226,1746,1747],{"class":253},"    grpc.",[226,1749,1750],{"class":236},"StatsHandler",[226,1752,1753],{"class":253},"(otelgrpc.",[226,1755,1756],{"class":236},"NewServerHandler",[226,1758,665],{"class":253},[226,1760,1761],{"class":228,"line":247},[226,1762,402],{"class":253},[226,1764,1765],{"class":228,"line":257},[226,1766,244],{"emptyLinePlaceholder":243},[226,1768,1769,1772,1774,1776,1779],{"class":228,"line":270},[226,1770,1771],{"class":253},"conn, err ",[226,1773,485],{"class":232},[226,1775,1737],{"class":253},[226,1777,1778],{"class":236},"NewClient",[226,1780,506],{"class":253},[226,1782,1783],{"class":228,"line":280},[226,1784,1785],{"class":253},"    addr,\n",[226,1787,1788,1790,1793,1795,1798],{"class":228,"line":290},[226,1789,1747],{"class":253},[226,1791,1792],{"class":236},"WithStatsHandler",[226,1794,1753],{"class":253},[226,1796,1797],{"class":236},"NewClientHandler",[226,1799,665],{"class":253},[226,1801,1802],{"class":228,"line":295},[226,1803,402],{"class":253},[15,1805,1806,1807,1810],{},"SQL в Go экосистеме менее \"однозначный\", чем HTTP. На практике часто используют ",[29,1808,1809],{},"otelsql","-подход и проверяют актуальность конкретного пакета под ваш driver.",[15,1812,1813],{},"Kafka и другие brokers: принцип важнее конкретной библиотеки. Producer inject trace context в message headers, consumer extract из headers и продолжает trace.",[15,1815,1816,1817,1820],{},"Для Echo можно использовать middleware вокруг стандартного ",[29,1818,1819],{},"http.Handler",":",[21,1822,1824],{"className":219,"code":1823,"language":221,"meta":222,"style":27},"e := echo.New()\ne.Use(echo.WrapMiddleware(func(next http.Handler) http.Handler {\n    return otelhttp.NewHandler(next, \"ratedesk-api\")\n}))\n",[29,1825,1826,1840,1881,1896],{"__ignoreMap":27},[226,1827,1828,1831,1833,1836,1838],{"class":228,"line":229},[226,1829,1830],{"class":253},"e ",[226,1832,485],{"class":232},[226,1834,1835],{"class":253}," echo.",[226,1837,629],{"class":236},[226,1839,1304],{"class":253},[226,1841,1842,1845,1848,1851,1854,1856,1858,1860,1863,1866,1868,1871,1873,1875,1877,1879],{"class":228,"line":240},[226,1843,1844],{"class":253},"e.",[226,1846,1847],{"class":236},"Use",[226,1849,1850],{"class":253},"(echo.",[226,1852,1853],{"class":236},"WrapMiddleware",[226,1855,419],{"class":253},[226,1857,413],{"class":232},[226,1859,419],{"class":253},[226,1861,1862],{"class":422},"next",[226,1864,1865],{"class":236}," http",[226,1867,212],{"class":253},[226,1869,1870],{"class":236},"Handler",[226,1872,466],{"class":253},[226,1874,1696],{"class":236},[226,1876,212],{"class":253},[226,1878,1870],{"class":236},[226,1880,593],{"class":253},[226,1882,1883,1885,1887,1889,1892,1894],{"class":228,"line":247},[226,1884,1006],{"class":232},[226,1886,1668],{"class":253},[226,1888,1671],{"class":236},[226,1890,1891],{"class":253},"(next, ",[226,1893,1677],{"class":260},[226,1895,402],{"class":253},[226,1897,1898],{"class":228,"line":257},[226,1899,1900],{"class":253},"}))\n",[15,1902,1903,1904,1907,1908,1910],{},"Проверьте, что span name и attribute ",[29,1905,1906],{},"http.route"," не превращаются в raw URL. Если instrumentation не знает route pattern, добавьте ",[29,1909,1906],{}," в своем middleware после match route.",[39,1912,1914],{"id":1913},"propagation","Propagation",[15,1916,1917,1918,212],{},"Trace работает только пока вы не потеряли ",[29,1919,1920],{},"context.Context",[15,1922,1923],{},"HTTP propagation:",[53,1925,1926,1931,1936],{},[56,1927,1928,62],{},[29,1929,1930],{},"traceparent",[56,1932,1933,62],{},[29,1934,1935],{},"tracestate",[56,1937,1938,1939,212],{},"optional ",[29,1940,1941],{},"baggage",[15,1943,1944],{},"Kafka propagation:",[53,1946,1947,1950,1953],{},[56,1948,1949],{},"headers;",[56,1951,1952],{},"producer inject;",[56,1954,1955],{},"consumer extract.",[15,1957,1958],{},"Пример для HTTP client обычно делает instrumentation, но вручную это выглядит так:",[21,1960,1962],{"className":219,"code":1961,"language":221,"meta":222,"style":27},"req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)\nif err != nil {\n    return err\n}\n\notel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))\n",[29,1963,1964,1980,1992,1999,2003,2007],{"__ignoreMap":27},[226,1965,1966,1969,1971,1974,1977],{"class":228,"line":229},[226,1967,1968],{"class":253},"req, err ",[226,1970,485],{"class":232},[226,1972,1973],{"class":253}," http.",[226,1975,1976],{"class":236},"NewRequestWithContext",[226,1978,1979],{"class":253},"(ctx, http.MethodPost, url, body)\n",[226,1981,1982,1984,1986,1988,1990],{"class":228,"line":240},[226,1983,1114],{"class":232},[226,1985,583],{"class":253},[226,1987,586],{"class":232},[226,1989,590],{"class":589},[226,1991,593],{"class":253},[226,1993,1994,1996],{"class":228,"line":247},[226,1995,1006],{"class":232},[226,1997,1998],{"class":253}," err\n",[226,2000,2001],{"class":228,"line":257},[226,2002,1065],{"class":253},[226,2004,2005],{"class":228,"line":270},[226,2006,244],{"emptyLinePlaceholder":243},[226,2008,2009,2012,2015,2018,2021,2024,2027],{"class":228,"line":280},[226,2010,2011],{"class":253},"otel.",[226,2013,2014],{"class":236},"GetTextMapPropagator",[226,2016,2017],{"class":253},"().",[226,2019,2020],{"class":236},"Inject",[226,2022,2023],{"class":253},"(ctx, propagation.",[226,2025,2026],{"class":236},"HeaderCarrier",[226,2028,2029],{"class":253},"(req.Header))\n",[15,2031,2032],{},"Consumer:",[21,2034,2036],{"className":219,"code":2035,"language":221,"meta":222,"style":27},"ctx := otel.GetTextMapPropagator().Extract(\n    context.Background(),\n    propagation.HeaderCarrier(headersFromMessage(msg)),\n)\nctx, span := tracer.Start(ctx, \"kafka.consume rates.updated\")\ndefer span.End()\n",[29,2037,2038,2056,2066,2081,2085,2103],{"__ignoreMap":27},[226,2039,2040,2043,2045,2047,2049,2051,2054],{"class":228,"line":229},[226,2041,2042],{"class":253},"ctx ",[226,2044,485],{"class":232},[226,2046,1381],{"class":253},[226,2048,2014],{"class":236},[226,2050,2017],{"class":253},[226,2052,2053],{"class":236},"Extract",[226,2055,506],{"class":253},[226,2057,2058,2061,2063],{"class":228,"line":240},[226,2059,2060],{"class":253},"    context.",[226,2062,1282],{"class":236},[226,2064,2065],{"class":253},"(),\n",[226,2067,2068,2071,2073,2075,2078],{"class":228,"line":247},[226,2069,2070],{"class":253},"    propagation.",[226,2072,2026],{"class":236},[226,2074,419],{"class":253},[226,2076,2077],{"class":236},"headersFromMessage",[226,2079,2080],{"class":253},"(msg)),\n",[226,2082,2083],{"class":228,"line":257},[226,2084,402],{"class":253},[226,2086,2087,2090,2092,2094,2096,2098,2101],{"class":228,"line":270},[226,2088,2089],{"class":253},"ctx, span ",[226,2091,485],{"class":232},[226,2093,1454],{"class":253},[226,2095,1457],{"class":236},[226,2097,1460],{"class":253},[226,2099,2100],{"class":260},"\"kafka.consume rates.updated\"",[226,2102,402],{"class":253},[226,2104,2105,2107,2109,2111],{"class":228,"line":280},[226,2106,1258],{"class":232},[226,2108,1472],{"class":253},[226,2110,1475],{"class":236},[226,2112,1304],{"class":253},[15,2114,2115],{},"Пример carrier для Kafka-like headers без привязки к конкретной библиотеке:",[21,2117,2119],{"className":219,"code":2118,"language":221,"meta":222,"style":27},"type MessageHeader struct {\n    Key   string\n    Value []byte\n}\n\ntype HeaderCarrier struct {\n    Headers []MessageHeader\n}\n\nfunc (c HeaderCarrier) Get(key string) string {\n    for _, h := range c.Headers {\n        if strings.EqualFold(h.Key, key) {\n            return string(h.Value)\n        }\n    }\n    return \"\"\n}\n\nfunc (c *HeaderCarrier) Set(key, value string) {\n    for i := range c.Headers {\n        if strings.EqualFold(c.Headers[i].Key, key) {\n            c.Headers[i].Value = []byte(value)\n            return\n        }\n    }\n    c.Headers = append(c.Headers, MessageHeader{Key: key, Value: []byte(value)})\n}\n\nfunc (c HeaderCarrier) Keys() []string {\n    keys := make([]string, 0, len(c.Headers))\n    for _, h := range c.Headers {\n        keys = append(keys, h.Key)\n    }\n    return keys\n}\n",[29,2120,2121,2134,2142,2150,2154,2158,2169,2177,2181,2185,2215,2231,2245,2255,2260,2264,2271,2275,2279,2309,2322,2333,2349,2354,2358,2362,2386,2390,2394,2416,2444,2456,2468,2472,2479],{"__ignoreMap":27},[226,2122,2123,2126,2129,2132],{"class":228,"line":229},[226,2124,2125],{"class":232},"type",[226,2127,2128],{"class":236}," MessageHeader",[226,2130,2131],{"class":232}," struct",[226,2133,593],{"class":253},[226,2135,2136,2139],{"class":228,"line":240},[226,2137,2138],{"class":253},"    Key   ",[226,2140,2141],{"class":232},"string\n",[226,2143,2144,2147],{"class":228,"line":247},[226,2145,2146],{"class":253},"    Value []",[226,2148,2149],{"class":232},"byte\n",[226,2151,2152],{"class":228,"line":257},[226,2153,1065],{"class":253},[226,2155,2156],{"class":228,"line":270},[226,2157,244],{"emptyLinePlaceholder":243},[226,2159,2160,2162,2165,2167],{"class":228,"line":280},[226,2161,2125],{"class":232},[226,2163,2164],{"class":236}," HeaderCarrier",[226,2166,2131],{"class":232},[226,2168,593],{"class":253},[226,2170,2171,2174],{"class":228,"line":290},[226,2172,2173],{"class":253},"    Headers []",[226,2175,2176],{"class":236},"MessageHeader\n",[226,2178,2179],{"class":228,"line":295},[226,2180,1065],{"class":253},[226,2182,2183],{"class":228,"line":305},[226,2184,244],{"emptyLinePlaceholder":243},[226,2186,2187,2189,2191,2194,2196,2198,2201,2203,2206,2208,2210,2213],{"class":228,"line":315},[226,2188,413],{"class":232},[226,2190,1402],{"class":253},[226,2192,2193],{"class":422},"c ",[226,2195,2026],{"class":236},[226,2197,466],{"class":253},[226,2199,2200],{"class":236},"Get",[226,2202,419],{"class":253},[226,2204,2205],{"class":422},"key",[226,2207,450],{"class":232},[226,2209,466],{"class":253},[226,2211,2212],{"class":232},"string",[226,2214,593],{"class":253},[226,2216,2217,2220,2223,2225,2228],{"class":228,"line":325},[226,2218,2219],{"class":232},"    for",[226,2221,2222],{"class":253}," _, h ",[226,2224,485],{"class":232},[226,2226,2227],{"class":232}," range",[226,2229,2230],{"class":253}," c.Headers {\n",[226,2232,2233,2236,2239,2242],{"class":228,"line":335},[226,2234,2235],{"class":232},"        if",[226,2237,2238],{"class":253}," strings.",[226,2240,2241],{"class":236},"EqualFold",[226,2243,2244],{"class":253},"(h.Key, key) {\n",[226,2246,2247,2250,2252],{"class":228,"line":345},[226,2248,2249],{"class":232},"            return",[226,2251,450],{"class":232},[226,2253,2254],{"class":253},"(h.Value)\n",[226,2256,2257],{"class":228,"line":355},[226,2258,2259],{"class":253},"        }\n",[226,2261,2262],{"class":228,"line":365},[226,2263,610],{"class":253},[226,2265,2266,2268],{"class":228,"line":375},[226,2267,1006],{"class":232},[226,2269,2270],{"class":260}," \"\"\n",[226,2272,2273],{"class":228,"line":389},[226,2274,1065],{"class":253},[226,2276,2277],{"class":228,"line":399},[226,2278,244],{"emptyLinePlaceholder":243},[226,2280,2281,2283,2285,2287,2289,2291,2293,2296,2298,2300,2302,2305,2307],{"class":228,"line":405},[226,2282,413],{"class":232},[226,2284,1402],{"class":253},[226,2286,2193],{"class":422},[226,2288,924],{"class":232},[226,2290,2026],{"class":236},[226,2292,466],{"class":253},[226,2294,2295],{"class":236},"Set",[226,2297,419],{"class":253},[226,2299,2205],{"class":422},[226,2301,434],{"class":253},[226,2303,2304],{"class":422},"value",[226,2306,450],{"class":232},[226,2308,476],{"class":253},[226,2310,2311,2313,2316,2318,2320],{"class":228,"line":410},[226,2312,2219],{"class":232},[226,2314,2315],{"class":253}," i ",[226,2317,485],{"class":232},[226,2319,2227],{"class":232},[226,2321,2230],{"class":253},[226,2323,2324,2326,2328,2330],{"class":228,"line":479},[226,2325,2235],{"class":232},[226,2327,2238],{"class":253},[226,2329,2241],{"class":236},[226,2331,2332],{"class":253},"(c.Headers[i].Key, key) {\n",[226,2334,2335,2338,2340,2343,2346],{"class":228,"line":509},[226,2336,2337],{"class":253},"            c.Headers[i].Value ",[226,2339,1133],{"class":232},[226,2341,2342],{"class":253}," []",[226,2344,2345],{"class":232},"byte",[226,2347,2348],{"class":253},"(value)\n",[226,2350,2351],{"class":228,"line":515},[226,2352,2353],{"class":232},"            return\n",[226,2355,2356],{"class":228,"line":527},[226,2357,2259],{"class":253},[226,2359,2360],{"class":228,"line":543},[226,2361,610],{"class":253},[226,2363,2364,2367,2369,2372,2375,2378,2381,2383],{"class":228,"line":554},[226,2365,2366],{"class":253},"    c.Headers ",[226,2368,1133],{"class":232},[226,2370,2371],{"class":236}," append",[226,2373,2374],{"class":253},"(c.Headers, ",[226,2376,2377],{"class":236},"MessageHeader",[226,2379,2380],{"class":253},"{Key: key, Value: []",[226,2382,2345],{"class":232},[226,2384,2385],{"class":253},"(value)})\n",[226,2387,2388],{"class":228,"line":571},[226,2389,1065],{"class":253},[226,2391,2392],{"class":228,"line":577},[226,2393,244],{"emptyLinePlaceholder":243},[226,2395,2396,2398,2400,2402,2404,2406,2409,2412,2414],{"class":228,"line":596},[226,2397,413],{"class":232},[226,2399,1402],{"class":253},[226,2401,2193],{"class":422},[226,2403,2026],{"class":236},[226,2405,466],{"class":253},[226,2407,2408],{"class":236},"Keys",[226,2410,2411],{"class":253},"() []",[226,2413,2212],{"class":232},[226,2415,593],{"class":253},[226,2417,2418,2421,2423,2426,2429,2431,2433,2436,2438,2441],{"class":228,"line":607},[226,2419,2420],{"class":253},"    keys ",[226,2422,485],{"class":232},[226,2424,2425],{"class":236}," make",[226,2427,2428],{"class":253},"([]",[226,2430,2212],{"class":232},[226,2432,434],{"class":253},[226,2434,2435],{"class":589},"0",[226,2437,434],{"class":253},[226,2439,2440],{"class":236},"len",[226,2442,2443],{"class":253},"(c.Headers))\n",[226,2445,2446,2448,2450,2452,2454],{"class":228,"line":613},[226,2447,2219],{"class":232},[226,2449,2222],{"class":253},[226,2451,485],{"class":232},[226,2453,2227],{"class":232},[226,2455,2230],{"class":253},[226,2457,2458,2461,2463,2465],{"class":228,"line":618},[226,2459,2460],{"class":253},"        keys ",[226,2462,1133],{"class":232},[226,2464,2371],{"class":236},[226,2466,2467],{"class":253},"(keys, h.Key)\n",[226,2469,2470],{"class":228,"line":635},[226,2471,610],{"class":253},[226,2473,2474,2476],{"class":228,"line":651},[226,2475,1006],{"class":232},[226,2477,2478],{"class":253}," keys\n",[226,2480,2481],{"class":228,"line":668},[226,2482,1065],{"class":253},[15,2484,2485,2486,2488,2489,2491],{},"В реальной Kafka-библиотеке carrier пишется поверх типа headers этой библиотеки. Главное - сохранить ",[29,2487,1930],{}," и ",[29,2490,1935],{}," как message headers, а не класть trace id в payload.",[15,2493,2494,2496],{},[29,2495,1941],{}," не место для PII. Все, что вы положили в baggage, может автоматически уйти downstream.",[15,2498,2499],{},"Что можно класть в baggage только при явной политике:",[53,2501,2502,2507,2512],{},[56,2503,2504,62],{},[29,2505,2506],{},"tenant_tier=free|pro|enterprise",[56,2508,2509,62],{},[29,2510,2511],{},"request_class=interactive|batch",[56,2513,2514,212],{},[29,2515,2516],{},"region=ru|eu|us",[15,2518,2519],{},"Что нельзя: email, phone, user id, token\u002Fsession id, номер карты, договор, паспорт, произвольный payload.",[39,2521,2523],{"id":2522},"semantic-conventions","Semantic conventions",[15,2525,2526,2527,434,2530,434,2533,2536],{},"OpenTelemetry semantic conventions - общий словарь. Не придумывайте ",[29,2528,2529],{},"db_name",[29,2531,2532],{},"method",[29,2534,2535],{},"status",", если есть стандартные ключи для HTTP, DB, RPC, messaging.",[15,2538,2539],{},"Примеры:",[21,2541,2543],{"className":219,"code":2542,"language":221,"meta":222,"style":27},"span.SetAttributes(\n    semconv.HTTPRequestMethodKey.String(\"POST\"),\n    semconv.HTTPRouteKey.String(\"\u002Fconvert\"),\n    semconv.HTTPResponseStatusCode(500),\n)\n",[29,2544,2545,2554,2568,2582,2597],{"__ignoreMap":27},[226,2546,2547,2550,2552],{"class":228,"line":229},[226,2548,2549],{"class":253},"span.",[226,2551,1489],{"class":236},[226,2553,506],{"class":253},[226,2555,2556,2559,2561,2563,2566],{"class":228,"line":240},[226,2557,2558],{"class":253},"    semconv.HTTPRequestMethodKey.",[226,2560,560],{"class":236},[226,2562,419],{"class":253},[226,2564,2565],{"class":260},"\"POST\"",[226,2567,540],{"class":253},[226,2569,2570,2573,2575,2577,2580],{"class":228,"line":247},[226,2571,2572],{"class":253},"    semconv.HTTPRouteKey.",[226,2574,560],{"class":236},[226,2576,419],{"class":253},[226,2578,2579],{"class":260},"\"\u002Fconvert\"",[226,2581,540],{"class":253},[226,2583,2584,2587,2590,2592,2595],{"class":228,"line":257},[226,2585,2586],{"class":253},"    semconv.",[226,2588,2589],{"class":236},"HTTPResponseStatusCode",[226,2591,419],{"class":253},[226,2593,2594],{"class":589},"500",[226,2596,540],{"class":253},[226,2598,2599],{"class":228,"line":270},[226,2600,402],{"class":253},[15,2602,2603],{},"Для доменных атрибутов используйте свой namespace:",[21,2605,2608],{"className":2606,"code":2607,"language":26,"meta":27},[24],"ratedesk.currency.from\nratedesk.currency.to\nratedesk.rate.source\n",[29,2609,2607],{"__ignoreMap":27},[39,2611,2613],{"id":2612},"errors-и-status","Errors и status",[15,2615,2616,2617,212],{},"Не каждая ошибка должна делать span ",[29,2618,1332],{},[15,2620,2539],{},[53,2622,2623,2630,2640,2651],{},[56,2624,2625,2626,2629],{},"validation error: span может остаться ",[29,2627,2628],{},"Ok",", HTTP status покажет 400;",[56,2631,2632,2635,2636,2639],{},[29,2633,2634],{},"rate not found",": доменный ",[29,2637,2638],{},"NotFound",", часто не production incident;",[56,2641,2642,2645,2646,434,2648,62],{},[29,2643,2644],{},"postgres timeout",": ",[29,2647,1576],{},[29,2649,2650],{},"SetStatus(codes.Error, ...)",[56,2652,2653,2654,212],{},"retry attempt: span event ",[29,2655,2656],{},"retry.attempt",[21,2658,2660],{"className":219,"code":2659,"language":221,"meta":222,"style":27},"if err != nil {\n    span.RecordError(err,\n        trace.WithAttributes(attribute.String(\"error.kind\", classify(err))),\n    )\n    span.SetStatus(codes.Error, \"dependency timeout\")\n    return err\n}\n",[29,2661,2662,2674,2683,2708,2712,2725,2731],{"__ignoreMap":27},[226,2663,2664,2666,2668,2670,2672],{"class":228,"line":229},[226,2665,1114],{"class":232},[226,2667,583],{"class":253},[226,2669,586],{"class":232},[226,2671,590],{"class":589},[226,2673,593],{"class":253},[226,2675,2676,2678,2680],{"class":228,"line":240},[226,2677,1486],{"class":253},[226,2679,1576],{"class":236},[226,2681,2682],{"class":253},"(err,\n",[226,2684,2685,2687,2690,2693,2695,2697,2700,2702,2705],{"class":228,"line":247},[226,2686,725],{"class":253},[226,2688,2689],{"class":236},"WithAttributes",[226,2691,2692],{"class":253},"(attribute.",[226,2694,560],{"class":236},[226,2696,419],{"class":253},[226,2698,2699],{"class":260},"\"error.kind\"",[226,2701,434],{"class":253},[226,2703,2704],{"class":236},"classify",[226,2706,2707],{"class":253},"(err))),\n",[226,2709,2710],{"class":228,"line":257},[226,2711,671],{"class":253},[226,2713,2714,2716,2718,2720,2723],{"class":228,"line":270},[226,2715,1486],{"class":253},[226,2717,1586],{"class":236},[226,2719,1589],{"class":253},[226,2721,2722],{"class":260},"\"dependency timeout\"",[226,2724,402],{"class":253},[226,2726,2727,2729],{"class":228,"line":280},[226,2728,1006],{"class":232},[226,2730,1998],{"class":253},[226,2732,2733],{"class":228,"line":290},[226,2734,1065],{"class":253},[39,2736,2738],{"id":2737},"logs-correlation","Logs correlation",[15,2740,2741,2742,2488,2745,2748],{},"У логов и traces должен быть общий ключ. Минимум: ",[29,2743,2744],{},"trace_id",[29,2746,2747],{},"span_id"," в structured logs.",[21,2750,2752],{"className":219,"code":2751,"language":221,"meta":222,"style":27},"func TraceLogAttrs(ctx context.Context) []slog.Attr {\n    sc := trace.SpanContextFromContext(ctx)\n    if !sc.IsValid() {\n        return nil\n    }\n    return []slog.Attr{\n        slog.String(\"trace_id\", sc.TraceID().String()),\n        slog.String(\"span_id\", sc.SpanID().String()),\n    }\n}\n",[29,2753,2754,2783,2798,2813,2820,2824,2838,2862,2884,2888],{"__ignoreMap":27},[226,2755,2756,2758,2761,2763,2765,2767,2769,2771,2774,2776,2778,2781],{"class":228,"line":229},[226,2757,413],{"class":232},[226,2759,2760],{"class":236}," TraceLogAttrs",[226,2762,419],{"class":253},[226,2764,423],{"class":422},[226,2766,426],{"class":236},[226,2768,212],{"class":253},[226,2770,431],{"class":236},[226,2772,2773],{"class":253},") []",[226,2775,36],{"class":236},[226,2777,212],{"class":253},[226,2779,2780],{"class":236},"Attr",[226,2782,593],{"class":253},[226,2784,2785,2788,2790,2792,2795],{"class":228,"line":240},[226,2786,2787],{"class":253},"    sc ",[226,2789,485],{"class":232},[226,2791,714],{"class":253},[226,2793,2794],{"class":236},"SpanContextFromContext",[226,2796,2797],{"class":253},"(ctx)\n",[226,2799,2800,2802,2805,2808,2811],{"class":228,"line":247},[226,2801,580],{"class":232},[226,2803,2804],{"class":232}," !",[226,2806,2807],{"class":253},"sc.",[226,2809,2810],{"class":236},"IsValid",[226,2812,1263],{"class":253},[226,2814,2815,2817],{"class":228,"line":257},[226,2816,599],{"class":232},[226,2818,2819],{"class":589}," nil\n",[226,2821,2822],{"class":228,"line":270},[226,2823,610],{"class":253},[226,2825,2826,2828,2830,2832,2834,2836],{"class":228,"line":280},[226,2827,1006],{"class":232},[226,2829,2342],{"class":253},[226,2831,36],{"class":236},[226,2833,212],{"class":253},[226,2835,2780],{"class":236},[226,2837,1704],{"class":253},[226,2839,2840,2843,2845,2847,2850,2853,2856,2858,2860],{"class":228,"line":290},[226,2841,2842],{"class":253},"        slog.",[226,2844,560],{"class":236},[226,2846,419],{"class":253},[226,2848,2849],{"class":260},"\"trace_id\"",[226,2851,2852],{"class":253},", sc.",[226,2854,2855],{"class":236},"TraceID",[226,2857,2017],{"class":253},[226,2859,560],{"class":236},[226,2861,665],{"class":253},[226,2863,2864,2866,2868,2870,2873,2875,2878,2880,2882],{"class":228,"line":295},[226,2865,2842],{"class":253},[226,2867,560],{"class":236},[226,2869,419],{"class":253},[226,2871,2872],{"class":260},"\"span_id\"",[226,2874,2852],{"class":253},[226,2876,2877],{"class":236},"SpanID",[226,2879,2017],{"class":253},[226,2881,560],{"class":236},[226,2883,665],{"class":253},[226,2885,2886],{"class":228,"line":305},[226,2887,610],{"class":253},[226,2889,2890],{"class":228,"line":315},[226,2891,1065],{"class":253},[15,2893,2894],{},"Тогда в Grafana можно идти:",[21,2896,2899],{"className":2897,"code":2898,"language":26,"meta":27},[24],"latency spike -> exemplar trace -> trace span -> related logs -> request_id\n",[29,2900,2898],{"__ignoreMap":27},[39,2902,2904],{"id":2903},"sampling","Sampling",[15,2906,2907],{},"Head sampling принимает решение в начале trace. Простой и дешевый вариант:",[21,2909,2911],{"className":219,"code":2910,"language":221,"meta":222,"style":27},"trace.ParentBased(trace.TraceIDRatioBased(0.10))\n",[29,2912,2913],{"__ignoreMap":27},[226,2914,2915,2918,2920,2922,2924,2926,2928],{"class":228,"line":229},[226,2916,2917],{"class":253},"trace.",[226,2919,745],{"class":236},[226,2921,742],{"class":253},[226,2923,750],{"class":236},[226,2925,419],{"class":253},[226,2927,755],{"class":589},[226,2929,2930],{"class":253},"))\n",[15,2932,2933],{},"Минус: можно случайно не сохранить интересный ошибочный trace.",[15,2935,2936],{},"Tail sampling принимает решение после того, как trace почти собран. Его обычно делают в Collector gateway:",[53,2938,2939,2942,2945,2948],{},[56,2940,2941],{},"оставить все error traces;",[56,2943,2944],{},"оставить traces дольше 1s;",[56,2946,2947],{},"оставить часть нормальных traces;",[56,2949,2950],{},"разные правила для staging\u002Fprod.",[15,2952,2953],{},"В учебном проекте достаточно 100% local sampling и README-секции: почему в production будет иначе.",[124,2955,2956,2968],{},[127,2957,2958],{},[130,2959,2960,2963,2965],{},[133,2961,2962],{"align":135},"Среда",[133,2964,2904],{"align":135},[133,2966,2967],{"align":135},"Почему",[141,2969,2970,2981,2992],{},[130,2971,2972,2975,2978],{},[146,2973,2974],{"align":135},"local",[146,2976,2977],{"align":135},"100%",[146,2979,2980],{"align":135},"Нужно видеть каждый учебный запрос.",[130,2982,2983,2986,2989],{},[146,2984,2985],{"align":135},"staging",[146,2987,2988],{"align":135},"100% или высокий ratio",[146,2990,2991],{"align":135},"Трафик небольшой, важно ловить regressions.",[130,2993,2994,2996,2999],{},[146,2995,202],{"align":135},[146,2997,2998],{"align":135},"parent-based ratio + tail sampling в gateway",[146,3000,3001],{"align":135},"Нельзя хранить все, но важно сохранять errors\u002Fslow traces.",[15,3003,3004,3005,3008],{},"Если нужен tail sampling в Collector, не выбрасывайте нужные traces слишком рано в SDK. Head sampling ",[29,3006,3007],{},"10%"," в приложении означает, что Collector уже никогда не увидит остальные 90%, включая часть ошибочных.",[39,3010,3012],{"id":3011},"otel-logs","OTel logs",[15,3014,3015],{},"OTel logs в Go сейчас стоит показывать аккуратно:",[53,3017,3018,3024],{},[56,3019,3020,3021,62],{},"основной production-путь: ",[29,3022,3023],{},"slog JSON -> stdout -> Alloy\u002FFluent Bit\u002Ffilelog -> Loki\u002FElastic",[56,3025,3026,3027,212],{},"advanced-путь: ",[29,3028,3029],{},"slog bridge \u002F OTLP logs -> OTel Collector -> logs backend",[15,3031,3032],{},"Причина: traces и metrics в Go стабильнее и привычнее для внедрения; logs через OTel полезны для unified pipeline, но не должны ломать понятный базовый путь.",[39,3034,3036],{"id":3035},"trace-quality-checklist","Trace quality checklist",[15,3038,3039],{},"Хороший trace:",[53,3041,3042,3045,3056,3059,3062,3065,3071,3074,3080],{},[56,3043,3044],{},"начинается на transport boundary: HTTP\u002FgRPC\u002Fconsumer;",[56,3046,3047,3048,434,3051,434,3053,62],{},"имеет понятные span names: ",[29,3049,3050],{},"HTTP POST \u002Fconvert",[29,3052,61],{},[29,3054,3055],{},"postgres.GetLatestRate",[56,3057,3058],{},"не создает span на каждую мелкую функцию;",[56,3060,3061],{},"содержит standard semantic attributes для HTTP\u002FDB\u002FRPC\u002Fmessaging;",[56,3063,3064],{},"содержит доменные attributes только с ограниченной кардинальностью;",[56,3066,3067,3068,3070],{},"записывает ",[29,3069,1576],{}," и status для системных ошибок;",[56,3072,3073],{},"добавляет events для retry, fallback, circuit breaker, cache miss;",[56,3075,3076,3077,3079],{},"не теряет ",[29,3078,1920],{}," между goroutines и adapters;",[56,3081,3082],{},"позволяет за 30 секунд понять, где набралась latency.",[39,3084,3086],{"id":3085},"что-сделать-в-ratedesk","Что сделать в RateDesk",[3088,3089,3090,3093,3096,3099,3102,3105,3114,3117],"ol",{},[56,3091,3092],{},"Инициализировать OTel SDK при старте сервиса.",[56,3094,3095],{},"Добавить resource attributes.",[56,3097,3098],{},"Включить W3C Trace Context.",[56,3100,3101],{},"Обернуть HTTP server\u002Fclient.",[56,3103,3104],{},"Добавить spans на use case, repository, Redis, Kafka\u002Fprovider.",[56,3106,3107,3108,3110,3111,3113],{},"Добавить ",[29,3109,2744],{},"\u002F",[29,3112,2747],{}," в logs.",[56,3115,3116],{},"Экспортировать traces в OTel Collector.",[56,3118,3119],{},"Проверить trace в Tempo\u002FJaeger.",[15,3121,3122],{},"Acceptance:",[53,3124,3125,3132,3135,3138,3143,3146,3149],{},[56,3126,3127,3128,3131],{},"один ",[29,3129,3130],{},"\u002Fconvert"," виден как полный trace;",[56,3133,3134],{},"DB\u002FRedis\u002Fprovider spans дочерние для use case;",[56,3136,3137],{},"errors записываются в spans;",[56,3139,3140,3141,62],{},"logs содержат ",[29,3142,2744],{},[56,3144,3145],{},"context не теряется между слоями;",[56,3147,3148],{},"README объясняет sampling и propagation;",[56,3150,3151],{},"в MR есть ссылка\u002Fскрин trace или сохраненный trace id из локального stack.",[3153,3154],"hr",{},[39,3156,3158],{"id":3157},"интерактивная-практика","Интерактивная практика",[3160,3161,3164,3167,3184],"quiz",{"answer":3162,"id":3163,"xp":1288},"1","obs-tracing-q1",[15,3165,3166],{},"Где обычно должен начинаться trace backend-запроса?",[3168,3169,3170],"template",{"v-slot:options":27},[53,3171,3172,3175,3178,3181],{},[56,3173,3174],{},"На transport boundary: HTTP\u002FgRPC\u002Fconsumer",[56,3176,3177],{},"В каждой маленькой helper-функции",[56,3179,3180],{},"Только внутри database driver",[56,3182,3183],{},"Только в logger middleware",[3168,3185,3186],{"v-slot:explanation":27},[15,3187,3188],{},"Trace должен описывать путь запроса через границы системы. Слишком мелкие spans превращают trace в шум.",[3190,3191,3194,3197,3335],"predict",{"answer":3192,"id":3193,"xp":921},"propagate\\nnew-root","obs-tracing-p1",[15,3195,3196],{},"Что выведет программа?",[3168,3198,3199],{"v-slot:code":27},[21,3200,3202],{"className":219,"code":3201,"language":221,"meta":27,"style":27},"package main\n\nimport \"fmt\"\n\nfunc TraceDecision(hasParent bool) string {\n    if hasParent {\n        return \"propagate\"\n    }\n    return \"new-root\"\n}\n\nfunc main() {\n    fmt.Println(TraceDecision(true))\n    fmt.Println(TraceDecision(false))\n}\n",[29,3203,3204,3211,3215,3227,3231,3252,3259,3266,3270,3277,3281,3285,3294,3314,3331],{"__ignoreMap":27},[226,3205,3206,3208],{"class":228,"line":229},[226,3207,233],{"class":232},[226,3209,3210],{"class":236}," main\n",[226,3212,3213],{"class":228,"line":240},[226,3214,244],{"emptyLinePlaceholder":243},[226,3216,3217,3219,3222,3225],{"class":228,"line":247},[226,3218,250],{"class":232},[226,3220,3221],{"class":260}," \"",[226,3223,3224],{"class":236},"fmt",[226,3226,267],{"class":260},[226,3228,3229],{"class":228,"line":257},[226,3230,244],{"emptyLinePlaceholder":243},[226,3232,3233,3235,3238,3240,3243,3246,3248,3250],{"class":228,"line":270},[226,3234,413],{"class":232},[226,3236,3237],{"class":236}," TraceDecision",[226,3239,419],{"class":253},[226,3241,3242],{"class":422},"hasParent",[226,3244,3245],{"class":232}," bool",[226,3247,466],{"class":253},[226,3249,2212],{"class":232},[226,3251,593],{"class":253},[226,3253,3254,3256],{"class":228,"line":280},[226,3255,580],{"class":232},[226,3257,3258],{"class":253}," hasParent {\n",[226,3260,3261,3263],{"class":228,"line":290},[226,3262,599],{"class":232},[226,3264,3265],{"class":260}," \"propagate\"\n",[226,3267,3268],{"class":228,"line":295},[226,3269,610],{"class":253},[226,3271,3272,3274],{"class":228,"line":305},[226,3273,1006],{"class":232},[226,3275,3276],{"class":260}," \"new-root\"\n",[226,3278,3279],{"class":228,"line":315},[226,3280,1065],{"class":253},[226,3282,3283],{"class":228,"line":325},[226,3284,244],{"emptyLinePlaceholder":243},[226,3286,3287,3289,3292],{"class":228,"line":335},[226,3288,413],{"class":232},[226,3290,3291],{"class":236}," main",[226,3293,1263],{"class":253},[226,3295,3296,3299,3302,3304,3307,3309,3312],{"class":228,"line":345},[226,3297,3298],{"class":253},"    fmt.",[226,3300,3301],{"class":236},"Println",[226,3303,419],{"class":253},[226,3305,3306],{"class":236},"TraceDecision",[226,3308,419],{"class":253},[226,3310,3311],{"class":589},"true",[226,3313,2930],{"class":253},[226,3315,3316,3318,3320,3322,3324,3326,3329],{"class":228,"line":355},[226,3317,3298],{"class":253},[226,3319,3301],{"class":236},[226,3321,419],{"class":253},[226,3323,3306],{"class":236},[226,3325,419],{"class":253},[226,3327,3328],{"class":589},"false",[226,3330,2930],{"class":253},[226,3332,3333],{"class":228,"line":365},[226,3334,1065],{"class":253},[3168,3336,3337],{"v-slot:hint":27},[15,3338,3339],{},"Если входящий trace context есть, продолжайте его; если нет - создавайте root span.",[3341,3342,3346,3364,3493],"code-task",{"expected":3343,"id":3344,"xp":3345},"record-error\\nadd-event\\nattribute","obs-tracing-ct1","20",[15,3347,3348,3349,3352,3353,3356,3357,3360,3361,212],{},"Реализуй ",[29,3350,3351],{},"SpanAction",": системная ошибка - ",[29,3354,3355],{},"record-error",", retry\u002Ffallback - ",[29,3358,3359],{},"add-event",", bounded route\u002Fstatus\u002Fdependency info - ",[29,3362,3363],{},"attribute",[3168,3365,3366],{"v-slot:template":27},[21,3367,3369],{"className":219,"code":3368,"language":221,"meta":27,"style":27},"package main\n\nimport \"fmt\"\n\nfunc SpanAction(signal string) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(SpanAction(\"dependency-error\"))\n    fmt.Println(SpanAction(\"retry\"))\n    fmt.Println(SpanAction(\"http-route\"))\n}\n",[29,3370,3371,3377,3381,3391,3395,3415,3422,3426,3430,3438,3455,3472,3489],{"__ignoreMap":27},[226,3372,3373,3375],{"class":228,"line":229},[226,3374,233],{"class":232},[226,3376,3210],{"class":236},[226,3378,3379],{"class":228,"line":240},[226,3380,244],{"emptyLinePlaceholder":243},[226,3382,3383,3385,3387,3389],{"class":228,"line":247},[226,3384,250],{"class":232},[226,3386,3221],{"class":260},[226,3388,3224],{"class":236},[226,3390,267],{"class":260},[226,3392,3393],{"class":228,"line":257},[226,3394,244],{"emptyLinePlaceholder":243},[226,3396,3397,3399,3402,3404,3407,3409,3411,3413],{"class":228,"line":270},[226,3398,413],{"class":232},[226,3400,3401],{"class":236}," SpanAction",[226,3403,419],{"class":253},[226,3405,3406],{"class":422},"signal",[226,3408,450],{"class":232},[226,3410,466],{"class":253},[226,3412,2212],{"class":232},[226,3414,593],{"class":253},[226,3416,3417,3419],{"class":228,"line":280},[226,3418,1006],{"class":232},[226,3420,3421],{"class":260}," \"todo\"\n",[226,3423,3424],{"class":228,"line":290},[226,3425,1065],{"class":253},[226,3427,3428],{"class":228,"line":295},[226,3429,244],{"emptyLinePlaceholder":243},[226,3431,3432,3434,3436],{"class":228,"line":305},[226,3433,413],{"class":232},[226,3435,3291],{"class":236},[226,3437,1263],{"class":253},[226,3439,3440,3442,3444,3446,3448,3450,3453],{"class":228,"line":315},[226,3441,3298],{"class":253},[226,3443,3301],{"class":236},[226,3445,419],{"class":253},[226,3447,3351],{"class":236},[226,3449,419],{"class":253},[226,3451,3452],{"class":260},"\"dependency-error\"",[226,3454,2930],{"class":253},[226,3456,3457,3459,3461,3463,3465,3467,3470],{"class":228,"line":325},[226,3458,3298],{"class":253},[226,3460,3301],{"class":236},[226,3462,419],{"class":253},[226,3464,3351],{"class":236},[226,3466,419],{"class":253},[226,3468,3469],{"class":260},"\"retry\"",[226,3471,2930],{"class":253},[226,3473,3474,3476,3478,3480,3482,3484,3487],{"class":228,"line":335},[226,3475,3298],{"class":253},[226,3477,3301],{"class":236},[226,3479,419],{"class":253},[226,3481,3351],{"class":236},[226,3483,419],{"class":253},[226,3485,3486],{"class":260},"\"http-route\"",[226,3488,2930],{"class":253},[226,3490,3491],{"class":228,"line":345},[226,3492,1065],{"class":253},[3168,3494,3495],{"v-slot:hints":27},[53,3496,3497,3500,3503],{},[56,3498,3499],{},"Error должен быть виден в span status\u002Ferror events.",[56,3501,3502],{},"Retry и fallback полезны как events во временной линии.",[56,3504,3505],{},"Attributes должны помогать фильтровать trace без высокой кардинальности.",[3507,3508,3509],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":27,"searchDepth":240,"depth":240,"links":3511},[3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525],{"id":41,"depth":240,"text":42},{"id":115,"depth":240,"text":116},{"id":215,"depth":240,"text":216},{"id":1359,"depth":240,"text":1360},{"id":1646,"depth":240,"text":1647},{"id":1913,"depth":240,"text":1914},{"id":2522,"depth":240,"text":2523},{"id":2612,"depth":240,"text":2613},{"id":2737,"depth":240,"text":2738},{"id":2903,"depth":240,"text":2904},{"id":3011,"depth":240,"text":3012},{"id":3035,"depth":240,"text":3036},{"id":3085,"depth":240,"text":3086},{"id":3157,"depth":240,"text":3158},1781022063279]