SDK를 사용하여 애플리케이션에서 측정항목 전송

이 문서에서는 SDK와 gRPC 내보내기 도구(SDK와 함께 사용하도록 권장되는 내보내기 도구)를 사용하여 애플리케이션에서 OTLP 엔드포인트로 직접 측정항목을 전송하는 방법을 보여줍니다. OTLP 엔드포인트는 http/proto, http/json, grpc를 비롯한 모든 OTLP 프로토콜을 지원합니다. 자세한 내용은 프로토콜 지원을 참조하세요.

애플리케이션 측정항목을 OTLP 엔드포인트로 직접 전송하면 OpenTelemetry 수집기가 인증 및 보강을 처리하지 않습니다. SDK를 사용하여 애플리케이션에서 측정항목을 전송할 때는 다음을 실행해야 합니다.

시작하기 전에

예를 실행하려면 필요한 API를 사용 설정하고 인증 사용자 인증 정보를 획득해야 합니다.

API 사용 설정

다음 명령어를 실행하여 Google Cloud 프로젝트에서 Cloud Monitoring API 및 Telemetry API를 사용 설정합니다.

gcloud services enable monitoring.googleapis.com telemetry.googleapis.com

인증 사용자 인증 정보 가져오기

다음 명령어를 실행하여 Google 애플리케이션 기본 사용자 인증 정보를 사용하도록 OTLP gRPC 내보내기 도구의 사용자 인증 정보를 구성합니다.

gcloud auth application-default login

언어별 예

이 섹션에서는 카운터 측정항목을 만들고 쓰는 언어별 샘플을 선택하여 제공합니다. 샘플 실행 및 코드 보기에 대한 탭을 선택합니다.

Go

이 예 구성 및 실행에 대한 자세한 내용은 README 파일에서 Go 샘플을 참조하세요.

// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"errors"
	"log"

	"go.opentelemetry.io/contrib/detectors/gcp"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/metric"
	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/oauth"
)

func main() {
	ctx := context.Background()

	creds, err := oauth.NewApplicationDefault(ctx)
	if err != nil {
		panic(err)
	}

	res, err := resource.New(
		ctx,
		// Use the GCP resource detector to detect information about the GCP platform
		resource.WithDetectors(gcp.NewDetector()),
		// Keep the default detectors
		resource.WithTelemetrySDK(),
		// Add attributes from environment variables
		resource.WithFromEnv(),
		// Add your own custom attributes to identify your application
		resource.WithAttributes(
			semconv.ServiceNameKey.String("example-application"),
		),
	)
	if errors.Is(err, resource.ErrPartialResource) || errors.Is(err, resource.ErrSchemaURLConflict) {
		log.Println(err)
	} else if err != nil {
		panic(err)
	}

	// Set endpoint with OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
	metricExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithDialOption(grpc.WithPerRPCCredentials(creds)))
	if err != nil {
		panic(err)
	}

	meterProvider := sdkmetric.NewMeterProvider(
		sdkmetric.WithResource(res),
		sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)),
	)

	defer func() {
		if err = meterProvider.Shutdown(ctx); err != nil {
			log.Println(err)
		}
	}()

	meter := meterProvider.Meter("github.com/GoogleCloudPlatform/opentelemetry-operations-go/example/metric/otlpgrpc")

	// Register counter value
	counter, err := meter.Int64Counter("counter-a")
	if err != nil {
		log.Fatalf("Failed to create counter: %v", err)
	}
	clabels := []attribute.KeyValue{attribute.Key("key").String("value")}
	counter.Add(ctx, 100, metric.WithAttributes(clabels...))
}

자바

이 예 구성 및 실행에 대한 자세한 내용은 자바 샘플의 README 파일을 참조하세요. Java sample

/*
 * Copyright 2025 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.cloud.opentelemetry.example.otlpmetric;

import com.google.auth.oauth2.GoogleCredentials;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class OTLPMetricExample {
  private static final String INSTRUMENTATION_SCOPE_NAME = OTLPMetricExample.class.getName();
  private static final Random RANDOM = new Random();

  private static OpenTelemetrySdk openTelemetrySdk;

  private static OpenTelemetrySdk setupMetricExporter() throws IOException {
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();

    // Update the SDK configured using environment variables and system properties
    // TODO: Replace this with the use of gcp-auth-extension
    AutoConfiguredOpenTelemetrySdk autoConfOTelSdk =
        AutoConfiguredOpenTelemetrySdk.builder()
            .addMetricExporterCustomizer(
                (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials))
            .build();
    return autoConfOTelSdk.getOpenTelemetrySdk();
  }

  // Modifies the metric exporter initially auto-configured using environment variables
  // This will invoke the header supplier function to compute the headers, which takes care of the
  // refresh.
  private static MetricExporter addAuthorizationHeaders(
      MetricExporter exporter, GoogleCredentials credentials) {
    if (exporter instanceof OtlpHttpMetricExporter) {
      return ((OtlpHttpMetricExporter) exporter)
          .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)).build();
    } else if (exporter instanceof OtlpGrpcMetricExporter) {
      return ((OtlpGrpcMetricExporter) exporter)
          .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)).build();
    }
    return exporter;
  }

  private static Map<String, String> getRequiredHeaderMap(GoogleCredentials credentials) {
    Map<String, String> gcpHeaders = new HashMap<>();
    try {
      credentials.refreshIfExpired();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    gcpHeaders.put("Authorization", "Bearer " + credentials.getAccessToken().getTokenValue());
    String configuredQuotaProjectId = credentials.getQuotaProjectId();
    if (configuredQuotaProjectId != null && !configuredQuotaProjectId.isEmpty()) {
      gcpHeaders.put("x-goog-user-project", configuredQuotaProjectId);
    }
    return gcpHeaders;
  }

  private static void myUseCase() {
    LongCounter counter =
        openTelemetrySdk
            .getMeter(INSTRUMENTATION_SCOPE_NAME)
            .counterBuilder("example_counter")
            .setDescription("Processed jobs")
            .setUnit("1")
            .build();
    doWork(counter);
  }

  private static void doWork(LongCounter counter) {
    try {
      for (int i = 0; i < 10; i++) {
        counter.add(RANDOM.nextInt(100));
        Thread.sleep(RANDOM.nextInt(1000));
      }
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }

  public static void main(String[] args) throws IOException {
    // Configure the OpenTelemetry pipeline with CloudMetric exporter
    openTelemetrySdk = setupMetricExporter();

    // Application-specific logic
    myUseCase();

    // Flush all buffered metrics
    CompletableResultCode completableResultCode = openTelemetrySdk.getSdkMeterProvider().shutdown();
    // wait till export finishes
    completableResultCode.join(10000, TimeUnit.MILLISECONDS);
  }
}

NodeJS

이 예 구성 및 실행에 대한 자세한 내용은 README 파일에서 NodeJS 샘플을(를) 참조하세요.

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import * as opentelemetry from '@opentelemetry/sdk-node';
import {AuthClient, GoogleAuth} from 'google-auth-library';
import {credentials} from '@grpc/grpc-js';
import {getResourceDetectors as getResourceDetectorsFromEnv} from '@opentelemetry/auto-instrumentations-node';
import {metrics, diag, DiagConsoleLogger} from '@opentelemetry/api';
import {OTLPMetricExporter} from '@opentelemetry/exporter-metrics-otlp-grpc';
import {PeriodicExportingMetricReader} from '@opentelemetry/sdk-metrics';

async function getAuthenticatedClient(): Promise<AuthClient> {
  const auth: GoogleAuth = new GoogleAuth({
    scopes: 'https://www.googleapis.com/auth/cloud-platform',
  });
  return await auth.getClient();
}

diag.setLogger(
  new DiagConsoleLogger(),
  opentelemetry.core.diagLogLevelFromString(
    opentelemetry.core.getStringFromEnv('OTEL_LOG_LEVEL'),
  ),
);

// App that exports metrics via gRPC with protobuf
async function main() {
  const authenticatedClient: AuthClient = await getAuthenticatedClient();

  const sdk = new opentelemetry.NodeSDK({
    metricReader: new PeriodicExportingMetricReader({
      // Export metrics every 10 seconds. 5 seconds is the smallest sample period allowed by
      // Cloud Monitoring.
      exportIntervalMillis: 10_000,
      exporter: new OTLPMetricExporter({
        credentials: credentials.combineChannelCredentials(
          credentials.createSsl(),
          credentials.createFromGoogleCredential(authenticatedClient),
        ),
      }),
    }),
    resourceDetectors: getResourceDetectorsFromEnv(),
  });
  sdk.start();

  // Create a meter
  const meter = metrics.getMeter('metrics-sample');

  // Create a counter instrument
  const counter = meter.createCounter('metric_name');
  // Record a measurement
  counter.add(10, {key: 'value'});

  // Wait for the metric to be exported
  await new Promise(resolve => {
    setTimeout(resolve, 11_000);
  });

  // Gracefully shut down the SDK to flush telemetry when the program exits
  process.on('SIGTERM', () => {
    sdk
      .shutdown()
      .then(() => diag.debug('OpenTelemetry SDK terminated'))
      .catch(error => diag.error('Error terminating OpenTelemetry SDK', error));
  });
}

main().catch(console.error);

Python

이 예 실행에 대한 자세한 내용은 README 파일의 README 파일에서 Python 샘플을 참조하세요.

#!/usr/bin/env python3
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import time

import google.auth
import google.auth.transport.grpc
import google.auth.transport.requests
import grpc
from google.auth.transport.grpc import AuthMetadataPlugin
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
    OTLPMetricExporter,
)
from opentelemetry.resourcedetector.gcp_resource_detector import GoogleCloudResourceDetector
from opentelemetry.sdk.resources import get_aggregated_resources
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader

"""
This is a sample script that exports OTLP metrics encoded as protobufs via gRPC. 
"""

credentials, project_id = google.auth.default()
request = google.auth.transport.requests.Request()
resource = get_aggregated_resources(
    [GoogleCloudResourceDetector(raise_on_error=True)]
)

auth_metadata_plugin = AuthMetadataPlugin(
    credentials=credentials, request=request
)
channel_creds = grpc.composite_channel_credentials(
    grpc.ssl_channel_credentials(),
    grpc.metadata_call_credentials(auth_metadata_plugin),
)

exporter = OTLPMetricExporter(credentials=channel_creds)
reader = PeriodicExportingMetricReader(exporter)
provider = MeterProvider(metric_readers=[reader],resource=resource)
meter = provider.get_meter("gcp.otlp.sample")
counter = meter.create_counter("sample.otlp.counter")


def do_work():
    counter.add(1)
    # do some work that the 'counter' will track
    print("doing some work...")


def do_work_repeatedly():
    try:
        while True:
            do_work()
            time.sleep(1)
    except KeyboardInterrupt:
        print("\nKeyboard Interrupt: Stopping work.")


do_work_repeatedly()

포드로 실행되는 애플리케이션에 하향식 API 사용

Kubernetes에서 애플리케이션을 포드로 실행하고 OpenTelemetry 수집기를 통해 전송하지 않는 경우 하향식 API를 사용하여 Kubernetes 리소스 속성을 설정해야 합니다.

env:
- name: POD_NAME
  valueFrom:
    fieldRef:
      fieldPath: metadata.name
- name: NAMESPACE_NAME
  valueFrom:
    fieldRef:
      fieldPath: metadata.namespace
- name: CONTAINER_NAME
  value: my-container-name
- name: OTEL_RESOURCE_ATTRIBUTES
  value: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME)

다음 단계