> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom metrics

Custom metrics let you track anything meaningful in your application — from the number of active users at a given moment, to how many invoices were generated in the last hour, to the current size of your database. By sending custom metrics to AppSignal, you can create graphs in your dashboards that relate this data to critical signals like error rates and response times, giving you the context to quickly identify and resolve issues.

Additionally, custom metrics help you work proactively rather than reactively. While logging helps you debug issues after they occur, custom metrics let you spot trends and potential problems before your customers notice. You can concentrate on data that matters to your business — such as active user counts, KPIs, or sales volumes — without sifting through log lines or incident lists. You can also set triggers on custom metrics to warn you when values fall outside expected ranges.

Custom metrics are not a replacement for [custom instrumentation](/ruby/instrumentation/instrumentation), but a complementary tool for making specific application data visible and measurable over time.

AppSignal offers three custom metric types:

| Type                        | Best for                              | Example use case                           |
| --------------------------- | ------------------------------------- | ------------------------------------------ |
| [Gauge](#gauge)             | Absolute values that change over time | Active users, database size, disk usage    |
| [Measurement](#measurement) | Ranges and distributions of values    | Response times, job durations, cart values |
| [Counter](#counter)         | Counting occurrences over time        | Invoices created, logins, errors triggered |

You can track custom metrics in [graphs](#graphing-custom-metrics) in your AppSignal dashboards.

### Gauge

A gauge records a value at a specific point in time. If you report multiple gauges with the same key within the same time window, only the last value is persisted.

**Use gauges when** you want to know the current state of something — the number of items that exist right now, or the current size of a resource. All AppSignal [host metrics](/metrics/host-metrics) are stored as gauges.

Gauges are often used with [minutely probes](/ruby/instrumentation/minutely-probes) to periodically report a snapshot value, such as querying the number of recently active shopping carts or open database connections every minute.

**Examples:** active user count, database size, disk usage, number of open connections.

<CodeGroup>
  ```ruby Ruby theme={null}
  # The first argument is a string, the second argument a number
  # Appsignal.set_gauge(metric_name, value)
  Appsignal.set_gauge("database_size", 100)
  Appsignal.set_gauge("database_size", 10)

  # Will create the metric "database_size" with the value 10
  ```

  ```elixir Elixir theme={null}
  # The first argument is a string, the second argument a number
  # Appsignal.set_gauge(metric_name, value)
  Appsignal.set_gauge("database_size", 100)
  Appsignal.set_gauge("database_size", 10)

  # Will create the metric "database_size" with the value 10
  ```

  ```javascript Node.js theme={null}
  const meter = Appsignal.client.metrics();

  // The first argument is a string, the second argument a number
  // meter.setGauge(metric_name, value)
  meter.setGauge("database_size", 100);
  meter.setGauge("database_size", 10);

  // Will create the metric "database_size" with the value 10
  ```

  ```python Python theme={null}
  # Import the AppSignal metric helper
  from appsignal import set_gauge

  # The first argument is a string, the second argument a number (int/float)
  # set_gauge(metric_name, value)
  set_gauge("database_size", 100)
  set_gauge("database_size", 10)

  # Will create the metric named "database_size" with the value 10
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  // Record gauge values
  Appsignal::setGauge('database_size', 100);
  Appsignal::setGauge('database_size', 10);

  // Will create the metric "database_size" with the value 10
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.metrics.DoubleGauge;
  import io.opentelemetry.api.metrics.Meter;

  public void recordMetrics() {
      // Get the meter from the global meter provider
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");

      // Create a gauge instrument
      DoubleGauge gauge = meter.gaugeBuilder("database_size").build();

      // Record gauge values
      gauge.set(100);
      gauge.set(10);

      // Will create the metric "database_size" with the value 10
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordMetrics() {
  	// Get the meter from the global meter provider
  	meter := otel.Meter("my-app")

  	// Create a gauge instrument
  	gauge, _ := meter.Float64Gauge("database_size")

  	// Record gauge values
  	gauge.Record(context.Background(), 100)
  	gauge.Record(context.Background(), 10)

  	// Will create the metric "database_size" with the value 10
  }
  ```
</CodeGroup>

<Tip>
  Starting from version 3.1.0, the Node.js integration allows you to set gauges and counters using the OpenTelemetry metrics provider. You must enable the agent's OpenTelemetry HTTP server to report these metrics to AppSignal; you can do this by setting the [enableOpentelemetryHttp](/nodejs/3.x/configuration/options#option-enable_opentelemetry_http) option to `true`.

  The Java and Go integrations also use OpenTelemetry metrics to report custom metrics to AppSignal.

  You can read more about OpenTelemetry metrics in the [OpenTelemetry metrics documentation](https://opentelemetry.io/docs/concepts/metrics/). For Node.js, see the [OpenTelemetry JS metrics documentation](https://opentelemetry.io/docs/languages/js/instrumentation/#metrics) for more information.
</Tip>

### Measurement

A measurement tracks a spread of values over time. AppSignal stores the average and count for each time window, letting you graph things like average duration, 90th/95th percentile, and throughput.

**Use measurements when** you are recording a value that varies across individual events and you want to understand its distribution — not just its current value. For example, you can track the duration of a critical background job (such as order confirmation emails) to spot slowdowns before they affect your users.

A measurement metric creates several metric fields:

* **Count:** how many times the helper was called. Useful for throughput graphs.
* **Mean:** the average metric value for the point in time.
* **90th percentile:** the 90th percentile of the metric value for the point in time.
* **95th percentile:** the 95th percentile of the metric value for the point in time.

**Examples:** HTTP response times, background job durations, user cart values, query execution times.

<CodeGroup>
  ```ruby Ruby theme={null}
  # The first argument is a string, the second argument a number
  # Appsignal.add_distribution_value(metric_name, value)
  Appsignal.add_distribution_value("memory_usage", 100)
  Appsignal.add_distribution_value("memory_usage", 110)

  # Will create a metric "memory_usage" with the mean field value 105
  # Will create a metric "memory_usage" with the count field value 2
  ```

  ```elixir Elixir theme={null}
  # The first argument is a string, the second argument a number
  # Appsignal.add_distribution_value(metric_name, value)
  Appsignal.add_distribution_value("memory_usage", 100)
  Appsignal.add_distribution_value("memory_usage", 110)

  # Will create a metric "memory_usage" with the mean field value 105
  # Will create a metric "memory_usage" with the count field value 2
  ```

  ```javascript JavaScript theme={null}
  const meter = Appsignal.client.metrics();

  // The first argument is a string, the second argument a number
  // meter.addDistributionValue(metric_name, value)
  meter.addDistributionValue("memory_usage", 100);
  meter.addDistributionValue("memory_usage", 110);

  // Will create a metric "memory_usage" with the mean field value 105
  // Will create a metric "memory_usage" with the count field value 2
  ```

  ```python Python theme={null}
  # Import the AppSignal metric helper
  from appsignal import add_distribution_value

  # The first argument is a string, the second argument a number (int/float)
  # add_distribution_value(metric_name, value)
  add_distribution_value("memory_usage", 100)
  add_distribution_value("memory_usage", 110)

  # Will create a metric "memory_usage" with the mean field value 105
  # Will create a metric "memory_usage" with the count field value 2
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  // Record distribution values
  Appsignal::addDistributionValue('memory_usage', 100);
  Appsignal::addDistributionValue('memory_usage', 110);

  // Will create a metric "memory_usage" with the mean field value 105
  // Will create a metric "memory_usage" with the count field value 2
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.metrics.DoubleHistogram;
  import io.opentelemetry.api.metrics.Meter;

  public void recordDistributions() {
      // Get the meter from the global meter provider
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");

      // Create a histogram instrument for distributions
      DoubleHistogram histogram = meter.histogramBuilder("memory_usage").build();

      // Record distribution values
      histogram.record(100);
      histogram.record(110);

      // Will create a metric "memory_usage" with the mean field value 105
      // Will create a metric "memory_usage" with the count field value 2
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordDistributions() {
  	// Get the meter from the global meter provider
  	meter := otel.Meter("my-app")

  	// Create a histogram instrument for distributions
  	histogram, _ := meter.Float64Histogram("memory_usage")

  	// Record distribution values
  	histogram.Record(context.Background(), 100)
  	histogram.Record(context.Background(), 110)

  	// Will create a metric "memory_usage" with the mean field value 105
  	// Will create a metric "memory_usage" with the count field value 2
  }
  ```
</CodeGroup>

### Counter

A counter accumulates a total count over a time window. Counter values are summed for each resolution — so at minutely resolution it shows the total for that minute, and at hourly resolution it shows the total for that hour.

**Use counters when** you want to track how many times something happened, not what value it had. Counters are especially useful for monitoring the frequency of business-critical events. For example, by tracking both orders placed and invoices created, you can verify that these related counters stay in sync and catch discrepancies early.

When the helper is called multiple times, the total/sum of all calls is persisted. Counters are non-monotonic: both positive and negative values are supported, so you can increment and decrement the same counter. For monotonic counters from other systems, there is no validation that counter values only ever increase.

**Examples:** orders placed, invoices created, user logins, background jobs queued, failed payment attempts.

<Tip>
  **Gauge vs. Counter:** Use a gauge to track *how many of something exist right
  now* (e.g., currently active users). Use a counter to track *how many times
  something happened* in a given period (e.g., new logins in the last minute).
</Tip>

<CodeGroup>
  ```ruby Ruby theme={null}
  # The first argument is a string, the second argument a number
  # Appsignal.increment_counter(metric_name, value)
  Appsignal.increment_counter("login_count", 1)
  Appsignal.increment_counter("login_count", 1)

  # Will create the metric "login_count" with the value 2 for a point in the minutely/hourly resolution
  ```

  ```elixir Elixir theme={null}
  # The first argument is a string, the second argument a number
  # Appsignal.increment_counter(metric_name, value)
  Appsignal.increment_counter("login_count", 1)
  Appsignal.increment_counter("login_count", 1)

  # Will create the metric "login_count" with the value 2 for a point in the minutely/hourly resolution
  ```

  ```javascript JavaScript theme={null}
  const meter = Appsignal.client.metrics();

  // The first argument is a string, the second argument a number
  // meter.incrementCounter(metric_name, value)
  meter.incrementCounter("login_count", 1);
  meter.incrementCounter("login_count", 1);

  // Will create the metric "login_count" with the value 2 for a point in the minutely/hourly resolution
  ```

  ```python Python theme={null}
  # Import the AppSignal metric helper
  from appsignal import increment_counter

  # The first argument is a string, the second argument a number (int/float)
  # increment_counter(metric_name, value)
  increment_counter("metric_name", 1)  # Increase the value by one
  increment_counter("metric_name", 1)
  increment_counter("metric_name", -1) # Decrease the value by one

  # Will create the metric named "metric_name" with the value 2 for a point in the minutely/hourly resolution
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  // Record counter values
  Appsignal::incrementCounter('login_count', 1);
  Appsignal::incrementCounter('login_count', 1);

  // Will create the metric "login_count" with the value 2 for a point in the minutely/hourly resolution
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.metrics.LongCounter;
  import io.opentelemetry.api.metrics.Meter;

  public void recordCounters() {
      // Get the meter from the global meter provider
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");

      // Create a counter instrument
      LongCounter counter = meter.counterBuilder("login_count").build();

      // Record counter values
      counter.add(1);
      counter.add(1);

      // Will create the metric "login_count" with the value 2 for a point in the minutely/hourly resolution
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordCounters() {
  	// Get the meter from the global meter provider
  	meter := otel.Meter("my-app")

  	// Create a counter instrument
  	counter, _ := meter.Int64Counter("login_count")

  	// Record counter values
  	counter.Add(context.Background(), 1)
  	counter.Add(context.Background(), 1)

  	// Will create the metric "login_count" with the value 2 for a point in the minutely/hourly resolution
  }
  ```
</CodeGroup>

<Warning>
  **Note**: In AppSignal, counters are designed to store integer values only.
  While the API accepts float values to facilitate integration, these values are internally converted to integers.

  Decimal values are rounded down to the nearest integer.
  The value 0.0001 becomes 0, and 1.2, 1.5, and 1.7 become 1.
</Warning>

## Metric naming

We recommend naming your metrics something easily recognizable. While you can wildcard parts of the metric name for dashboard creation, we recommend you only use this for small grouping and not use IDs in metric names.

Metric names only support numbers, letters, dots and underscores (`[a-z0-9._]`) as valid characters. Any other characters will be replaced with an underscore by our processor. You can find the list of metrics as processed on the ["Add Dashboard"](https://appsignal.com/redirect-to/app?to=dashboard\&overlay=dashboardForm).

Some examples of good metric names are:

* `database_size`
* `account_count`
* `users.count`
* `notifier.failed`
* `notifier.perform`
* `notifier.success`

By default AppSignal already tracks metrics for your application, such as [host metrics](/metrics/host-metrics). See the metrics list on the ["Add Dashboard"](https://appsignal.com/redirect-to/app?to=dashboard\&overlay=dashboardForm) page for the metrics that are already available for your app.

<Warning>
  **Note**: We **do not** recommend adding dynamic values to your metric names
  like so: `eu.database_size`, `us.database_size` and `asia.database_size`. This
  creates multiple metrics that serve the same purpose. Instead we recommend
  using [metric tags](#metric-tags) for this.
</Warning>

## Metric values

Metrics only support numbers as valid values. Any other value will be silently ignored or will raise an error as triggered by the implementation language number parser. For Ruby and Elixir we support a [double](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) and [integer](https://en.wikipedia.org/wiki/Integer) as valid values:

<CodeGroup>
  ```ruby Ruby theme={null}
  # Integer
  Appsignal.increment_counter("login_count", 1)
  # Double
  Appsignal.increment_counter("assignment_completed", 0.12)
  ```

  ```elixir Elixir theme={null}
  # Integer
  Appsignal.increment_counter("login_count", 1)
  # Double
  Appsignal.increment_counter("assignment_completed", 0.12)
  ```

  ```javascript JavaScript theme={null}
  const meter = Appsignal.client.metrics();

  // Integer
  meter.incrementCounter("login_count", 1);
  // Double
  meter.incrementCounter("assignment_completed", 0.12);
  ```

  ```python Python theme={null}
  from appsignal import increment_counter

  # Integer
  increment_counter("login_count", 1)
  # Double
  increment_counter("assignment_completed", 0.12)
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  // Integer
  Appsignal::incrementCounter('login_count', 1);
  // Double
  Appsignal::incrementCounter('login_count', 0.12);
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.metrics.DoubleCounter;
  import io.opentelemetry.api.metrics.LongCounter;
  import io.opentelemetry.api.metrics.Meter;

  public void recordValues() {
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");

      // Integer counter
      LongCounter longCounter = meter.counterBuilder("login_count").build();
      longCounter.add(1);

      // Double counter
      DoubleCounter doubleCounter = meter.counterBuilder("assignment_completed")
          .ofDoubles()
          .build();
      doubleCounter.add(0.12);
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordValues() {
  	meter := otel.Meter("my-app")

  	// Integer counter
  	intCounter, _ := meter.Int64Counter("login_count")
  	intCounter.Add(context.Background(), 1)

  	// Float counter
  	floatCounter, _ := meter.Float64Counter("assignment_completed")
  	floatCounter.Add(context.Background(), 0.12)
  }
  ```
</CodeGroup>

Note: In Node.js, only the `number` type is a valid value.

### Value formatting {/* id: metric-values-value-formatting */}

AppSignal graphs have several display formats, such as numbers, file sizes, durations,
etc. These formats help in presenting the metric values in a human-readable way.

Selecting a value formatter input does not affect the data stored in our systems, only how it's displayed.

To show metric values correctly using these formatters, please check the table below how the value should be reported.

| Formatter  | Reported value             | Description                                                                                                                                                                                                                                                                                                                    |
| ---------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Number     | Display value              | A human-readable formatted number. The values should be reported on the same scale as they are displayed. The value `1` is displayed as "`1`", `10_000` as "`10 K`" and `1_000_000` is displayed as "`1 M`".                                                                                                                   |
| Percentage | Display value              | A metric value formatted as a percentage. The values should be reported on the same scale as they are displayed. The value `40` is displayed as "`40 %`".                                                                                                                                                                      |
| Throughput | Display value              | A metric value formatted as requests per minute/hour. The values should be reported on the same scale as they are displayed. It will display the throughput formatted as a number for both the minute and the hour. The value `10_000` is displayed "`10k / hour 166 / min`". Commonly used for [`counter`](#counter) metrics. |
| Duration   | Milliseconds               | A duration of time. The values should be reported as milliseconds. The value `100` is displayed as "`100 ms`" and 60\_000 as "`60 sec`". Commonly used for [`measurement`](/metrics/custom#measurement) metric.                                                                                                                |
| File size  | [Customizable](#file-size) | A file size formatted as megabytes by default. `1.0` megabytes is displayed as `1Mb`. What file size unit the reported metric value is read as can be customized in the graph builder.                                                                                                                                         |

#### File size

Metric values that represent a file size can use the file size formatter. To specify which unit of size the reported value is the file size formatter allows for several input values.

The available options are:

* `Size in Bit`
* `Size in Bytes`
* `Size in Kilobits`
* `Size in Kilobytes`
* `Size in Megabytes`

When sending a metric with the following value:

<CodeGroup>
  ```ruby Ruby theme={null}
  Appsignal.set_gauge("database_size", 1024)
  ```

  ```elixir Elixir theme={null}
  Appsignal.set_gauge("database_size", 1024)
  ```

  ```javascript JavaScript theme={null}
  const meter = Appsignal.client.metrics();

  meter.setGauge("database_size", 1024);
  ```

  ```python Python theme={null}
  # Import the AppSignal metric helper
  from appsignal import set_gauge

  set_gauge("database_size", 1024)
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  Appsignal::setGauge('database_size', 1024);
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.metrics.DoubleGauge;
  import io.opentelemetry.api.metrics.Meter;

  public void recordFileSize() {
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");
      DoubleGauge gauge = meter.gaugeBuilder("database_size").build();

      gauge.set(1024);
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordFileSize() {
  	meter := otel.Meter("my-app")
  	gauge, _ := meter.Float64Gauge("database_size")

  	gauge.Record(context.Background(), 1024)
  }
  ```
</CodeGroup>

The graph will render the following display value for the specified file size formatter:

* `Size in Bit` will render "128 Bytes"
* `Size in Bytes` will render "1 KB"
* `Size in Kilobits` will render "128 KB"
* `Size in Kilobytes` will render "1 MB"
* `Size in Megabytes` will render "1 GB"

## Metric tags

<Tip>
  Custom metric tags require the following AppSignal package/gem version or
  higher:

  <ul>
    <li>Ruby gem version `2.6.0`</li>
    <li>Elixir package `1.6.0`</li>
    <li>Node.js package `1.0.0`</li>
    <li>Python package `0.3.0`</li>
  </ul>
</Tip>

A single metric can consist of various groups of data; for example, a 'total\_orders' custom metric could consist of pending and processed orders.

You can add multiple tags to metrics for deeper insights into the metric data AppSignal is reporting. Each tag will be represented by its line on an AppSignal graph.

By default, the AppSignal metric helpers do not set tags on a custom metric.

How you can use tags in AppSignal graphs:

Tags can "label" a line in the graph legend, making it easier to see what each line represents when you hover your mouse over them.
Filtering with tags can show the same metric in different contexts on different graphs.

To get the most out of metric tags, we advise the following:

* **Be consistent with tagging:** For optimal tracking and graph legibility, we advise against reporting a custom metric both with and without tags. Reporting the same metric in different ways can create confusion and make it difficult to interpret the data accurately.

* **Always use the same metric tags when:** We recommend using the same combination of tags whenever you report a metric. For example, if a metric uses tags A and B in one area of your app, it should use them in all areas. Avoid changing the tags, as consistent tagging will make graphing easier.

* **Use a limited range of values for metric tags:** To avoid overly complex or illegible graphs, we recommend using a limited range of tags on your custom metrics, such as regions (EU, US, and Asia). You can perform a broader analysis of your app's metric data when using a limited and precise range of tags for all of your app metric data.

In general when implementing metric tagging, consider developing a robust tagging strategy that can be applied consistently across your application and will need little to no adjustments in the long term.

<CodeGroup>
  ```ruby Ruby theme={null}
  Appsignal.set_gauge("database_size", 100, :region => "eu")
  Appsignal.set_gauge("database_size",  50, :region => "us")
  Appsignal.set_gauge("database_size", 200, :region => "asia")

  # Multiple tags per metric
  Appsignal.set_gauge("my_metric_name", 100, :tag_a => "a", :tag_b => "b")
  Appsignal.set_gauge("my_metric_name", 10, :tag_a => "a", :tag_b => "b")
  Appsignal.set_gauge("my_metric_name", 200, :tag_a => "b", :tag_b => "c")
  ```

  ```elixir Elixir theme={null}
  Appsignal.set_gauge("database_size", 100, %{region: "eu"})
  Appsignal.set_gauge("database_size",  50, %{region: "us"})
  Appsignal.set_gauge("database_size", 200, %{region: "asia"})

  # Multiple tags per metric
  Appsignal.set_gauge("my_metric_name", 100, %{tag_a: "a", tag_b: "b"})
  Appsignal.set_gauge("my_metric_name", 10, %{tag_a: "a", tag_b: "b"})
  Appsignal.set_gauge("my_metric_name", 200, %{tag_a: "b", tag_b: "c"})
  ```

  ```javascript JavaScript theme={null}
  const meter = Appsignal.client.metrics();

  meter.setGauge("database_size", 100, { region: "eu" });
  meter.setGauge("database_size", 50, { region: "us" });
  meter.setGauge("database_size", 200, { region: "asia" });

  // Multiple tags per metric
  meter.setGauge("my_metric_name", 100, { tag_a: "a", tag_b: "b" });
  meter.setGauge("my_metric_name", 10, { tag_a: "a", tag_b: "b" });
  meter.setGauge("my_metric_name", 200, { tag_a: "b", tag_b: "c" });
  ```

  ```python Python theme={null}
  from appsignal import set_gauge

  set_gauge("database_size", 100, {"region": "eu"})
  set_gauge("database_size",  50, {"region": "us"})
  set_gauge("database_size", 200, {"region": "asia"})

  # Multiple tags per metric
  set_gauge("my_metric_name", 100, {"tag_a": "a", "tag_b": "b"})
  set_gauge("my_metric_name",  10, {"tag_a": "a", "tag_b": "b"})
  set_gauge("my_metric_name", 200, {"tag_a": "b", "tag_b": "c"})
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  // Record gauge values with attributes (tags)
  Appsignal::setGauge('database_size', 100, ['region' => 'eu']);
  Appsignal::setGauge('database_size', 50, ['region' => 'us']);
  Appsignal::setGauge('database_size', 200, ['region' => 'asia']);

  // Multiple tags per metric
  Appsignal::setGauge('my_metric_name', 100, ['tag_a' => 'a', 'tag_b' => 'b']);
  Appsignal::setGauge('my_metric_name', 10, ['tag_a' => 'a', 'tag_b' => 'b']);
  Appsignal::setGauge('my_metric_name', 200, ['tag_a' => 'b', 'tag_b' => 'c']);
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.common.AttributeKey;
  import io.opentelemetry.api.common.Attributes;
  import io.opentelemetry.api.metrics.DoubleGauge;
  import io.opentelemetry.api.metrics.Meter;

  public void recordTaggedMetrics() {
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");
      DoubleGauge gauge = meter.gaugeBuilder("database_size").build();

      // Record gauge values with attributes (tags)
      gauge.set(100, Attributes.of(
          AttributeKey.stringKey("region"), "eu"
      ));
      gauge.set(50, Attributes.of(
          AttributeKey.stringKey("region"), "us"
      ));
      gauge.set(200, Attributes.of(
          AttributeKey.stringKey("region"), "asia"
      ));

      // Multiple tags per metric
      DoubleGauge myGauge = meter.gaugeBuilder("my_metric_name").build();
      myGauge.set(100, Attributes.of(
          AttributeKey.stringKey("tag_a"), "a",
          AttributeKey.stringKey("tag_b"), "b"
      ));
      myGauge.set(10, Attributes.of(
          AttributeKey.stringKey("tag_a"), "a",
          AttributeKey.stringKey("tag_b"), "b"
      ));
      myGauge.set(200, Attributes.of(
          AttributeKey.stringKey("tag_a"), "b",
          AttributeKey.stringKey("tag_b"), "c"
      ));
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/attribute"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordTaggedMetrics() {
  	meter := otel.Meter("my-app")
  	gauge, _ := meter.Float64Gauge("database_size")

  	// Record gauge values with attributes (tags)
  	gauge.Record(context.Background(), 100, metric.WithAttributes(
  		attribute.String("region", "eu"),
  	))
  	gauge.Record(context.Background(), 50, metric.WithAttributes(
  		attribute.String("region", "us"),
  	))
  	gauge.Record(context.Background(), 200, metric.WithAttributes(
  		attribute.String("region", "asia"),
  	))

  	// Multiple tags per metric
  	myGauge, _ := meter.Float64Gauge("my_metric_name")
  	myGauge.Record(context.Background(), 100, metric.WithAttributes(
  		attribute.String("tag_a", "a"),
  		attribute.String("tag_b", "b"),
  	))
  	myGauge.Record(context.Background(), 10, metric.WithAttributes(
  		attribute.String("tag_a", "a"),
  		attribute.String("tag_b", "b"),
  	))
  	myGauge.Record(context.Background(), 200, metric.WithAttributes(
  		attribute.String("tag_a", "b"),
  		attribute.String("tag_b", "c"),
  	))
  }
  ```
</CodeGroup>

## Rendering metric with and without tags

If you created a custom metric and you have multiple tags associated with it, you can render the metric with and without the tag at the same time in a graph.

<CodeGroup>
  ```ruby Ruby theme={null}
  Appsignal.increment_counter("sign_ups", 1, region: "eu")
  Appsignal.increment_counter("sign_ups", 1)
  ```

  ```elixir Elixir theme={null}
  Appsignal.increment_counter("sign_ups", 1, %{region: "eu"})
  Appsignal.increment_counter("sign_ups", 1)
  ```

  ```javascript JavaScript theme={null}
  const meter = Appsignal.client.metrics();

  meter.incrementCounter("sign_ups", 1, { region: "eu" });
  meter.incrementCounter("sign_ups", 1);
  ```

  ```python Python theme={null}
  from appsignal import increment_counter

  increment_counter("sign_ups", 1, {"region": "eu"})
  increment_counter("sign_ups", 1)
  ```

  ```php PHP theme={null}
  <?php
  use Appsignal\Appsignal;

  Appsignal::incrementCounter('sign_ups', 1, ['region' => 'eu']);
  Appsignal::incrementCounter('sign_ups', 1);
  ```

  ```java Java theme={null}
  import io.opentelemetry.api.GlobalOpenTelemetry;
  import io.opentelemetry.api.common.AttributeKey;
  import io.opentelemetry.api.common.Attributes;
  import io.opentelemetry.api.metrics.LongCounter;
  import io.opentelemetry.api.metrics.Meter;

  public void recordMixedMetrics() {
      Meter meter = GlobalOpenTelemetry.getMeter("my-app");
      LongCounter counter = meter.counterBuilder("sign_ups").build();

      counter.add(1, Attributes.of(
          AttributeKey.stringKey("region"), "eu"
      ));
      counter.add(1);
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"go.opentelemetry.io/otel"
  	"go.opentelemetry.io/otel/attribute"
  	"go.opentelemetry.io/otel/metric"
  )

  func recordMixedMetrics() {
  	meter := otel.Meter("my-app")
  	counter, _ := meter.Int64Counter("sign_ups")

  	counter.Add(context.Background(), 1, metric.WithAttributes(
  		attribute.String("region", "eu"),
  	))
  	counter.Add(context.Background(), 1)
  }
  ```
</CodeGroup>

## Graphing Custom Metrics

Once you've started recording custom metrics, you can track them in custom graphs using the Graph Builder. Navigate to an existing dashboard or use the "Add dashboard" button in the Dashboard navigation to create a new one, then click "Add graph".

<img src="https://mintcdn.com/appsignal-715f5a51/4TRZP0Sq9Zq7PAPW/assets/images/screenshots/metrics/custom/graph-builder.png?fit=max&auto=format&n=4TRZP0Sq9Zq7PAPW&q=85&s=095f9711aa9531e138596c955b6a81c2" alt="Graph Builder" width="2304" height="1720" data-path="assets/images/screenshots/metrics/custom/graph-builder.png" />

When creating a graph, you can select which metrics and tags you want to chart and configure your graph's legends and labels. Once created, the graph will be added to the dashboard and display all recorded custom metric data for the specified period of time.

You can read more about dashboards and graphs in our [Dashboard documentation](/metrics/dashboards).
