Verified Commit c2a6b1ba authored by Doug Barrett's avatar Doug Barrett 🔴 Committed by GitLab
Browse files

Merge branch 'docs/consolidate-how-to-fix-into-field-standardization' into 'master'

Consolidate and improve FIELD_STANDARDIZATION.md

See merge request !240

Merged-by: default avatarDoug Barrett <dbarrett@gitlab.com>
Approved-by: default avatarMatias Alvarez <malvarez@gitlab.com>
Reviewed-by: default avatarMatias Alvarez <malvarez@gitlab.com>
parents 3354c151 d7ff8b2c
Loading
Loading
Loading
Loading
Loading
+145 −125
Original line number Diff line number Diff line
@@ -2,134 +2,172 @@

## Overview

The LabKit Field Validator detects when your code uses deprecated logging field names and helps you migrate to standardized fields. This supports the [Observability Field Standardisation initiative](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/observability_field_standardisation/).
The LabKit Field Validator detects deprecated logging field names and helps
migrate them to standardized fields.
It supports the [Observability Field Standardisation initiative](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/observability_field_standardisation/).

**Goal:** Standardize logging field names across GitLab so logs are queryable and actionable across all systems.
The validator intercepts logging calls in non-production environments, detects
deprecated fields, and compares them against a frozen baseline stored in
`.labkit_logging_todo.yml`.
New offenses raise an error. Known offenses are allowed.

**How it works:** The validator intercepts logging calls during development and testing, detects deprecated fields, and compares them against a frozen baseline. New offenses fail CI; known offenses are tracked in `.labkit_logging_todo.yml`. The validator is **not** active in production environments.
For the architectural decision and rationale, see
[ADR: Dynamic Runtime Linting](./architecture/decisions/001_field_standardization_dynamic_runtime_linting.md).

For the architectural decision and rationale, see [ADR: Dynamic Runtime Linting](./architecture/decisions/001_field_standardization_dynamic_runtime_linting.md).
## Key concepts

## Key Concepts
Offense
: A unique combination of file path, deprecated field, and logger class.
  Multiple log calls in the same file using the same deprecated field count as
  one offense.
  An offense exists until the deprecated field is entirely removed from the
  file.

**Offense**
- A unique combination of [File Path] + [Deprecated Field] + [Logger Class]
- Multiple log calls in the same file using the same deprecated field = 1 offense
- Offenses exist until the deprecated field is entirely removed from the file
TODO Baseline
: The list of known offenses in `.labkit_logging_todo.yml`.
  The baseline prevents regression while allowing incremental cleanup.

**TODO Baseline**
- A list of known offenses tracked in `.labkit_logging_todo.yml`
- Existing offenses in this baseline are allowed
- Any new offenses detected during development raise an error
- Prevents regression while allowing incremental cleanup
## Quick start

## Quick Start
1. Initialize the todo file:

### First-Time Setup

1. **Initialize the todo file:**

   ```bash
   ```shell
   bundle exec labkit-logging init
   ```

   This creates `.labkit_logging_todo.yml` with `skip_ci_failure: true`, which allows CI to pass while collecting the initial baseline.
   This creates `.labkit_logging_todo.yml` with `skip_ci_failure: true`, which
   allows CI to pass while outputting found offenses.
   These are later collected to create the baseline.

2. **Commit and push:**
1. Commit and push:

   ```bash
   ```shell
   git add .labkit_logging_todo.yml
   git commit -m "Add LabKit logging todo baseline"
   git push
   ```

3. **Wait for CI to complete**, then fetch the baseline:
1. Wait for CI to complete, then fetch the baseline:

   ```bash
   ```shell
   bundle exec labkit-logging fetch <project> <pipeline_id>
   ```

   For example:
   ```bash

   ```shell
   bundle exec labkit-logging fetch gitlab-org/gitlab 12345
   ```

   This fetches all detected offenses from the CI pipeline logs and populates the todo file. The `skip_ci_failure` flag is automatically removed.
   This fetches detected offenses from CI pipeline logs and populates the todo
   file.
   The `skip_ci_failure` flag is automatically removed.

4. **Commit the populated baseline:**
1. Commit the populated baseline:

   ```bash
   ```shell
   git add .labkit_logging_todo.yml
   git commit -m "Populate LabKit logging todo baseline"
   git push
   ```

Future CI runs will now enforce this baseline—new offenses will fail the pipeline.

## Developer Workflow
Future CI runs enforce this baseline.
New offenses fail the pipeline.

### Fixing Offenses (Recommended)
## How to deprecate a field

Replace deprecated fields with standard constants:
1. In `lib/labkit/fields.rb`, confirm the standard field constant exists in
   `Labkit::Fields`. Add it if it does not exist:

   ```ruby
# Before
logger.info(user_id: current_user.id)
   GL_PROJECT_ID = "gl_project_id"
   ```

# After
logger.info(Labkit::Fields::GL_USER_ID => current_user.id)
1. In the same file, add the deprecated names to
   `Labkit::Fields::Deprecated::MAPPINGS`:

   ```ruby
   Fields::GL_PROJECT_ID => %w[project_id projectid],
   ```

When you fix an offense, it's automatically removed from the baseline on the next test run. Run your tests locally to verify:
1. Create a Kibana field alias in the relevant index patterns to map the
   deprecated field to the standard field.
   This ensures log queries continue to work during the migration period.

```bash
LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec
1. Release a new version of `labkit-ruby`.

1. In each consumer repository (for example, `gitlab-org/gitlab`), the MR to
   update `labkit-ruby` fails with new offenses.
   To add these offenses to the baseline, run `fetch` against the failed
   pipeline and push the updated baseline to the branch:

   ```shell
   bundle exec labkit-logging fetch <project> <pipeline_id>
   git add .labkit_logging_todo.yml
   git commit -m "Add new deprecated field offenses to baseline"
   git push
   ```

### Adding Offenses Temporarily
## How to fix an offense

If you can't fix an offense immediately, add it to the baseline:
The field validator runs during any non-production process that exercises
logging code paths, including tests, local development, and CI pipelines.
When a log call uses the standard field instead of the deprecated field, the
validator detects the fix automatically and removes the offense from the
baseline.

```bash
LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec
1. Replace the deprecated field with the standard field constant.

1. Remove the deprecated field entirely from each log call in the file.
   Adding the new field is not enough. The old key must be deleted.

1. Run any process that exercises the logging code path:

   ```shell
   bundle exec rspec
   ```

This updates `.labkit_logging_todo.yml` with any new offenses found during the test run. Commit the updated file with your changes.
   The offense is automatically removed from `.labkit_logging_todo.yml`.

**Note:** Justify in your MR why you can't fix immediately. Keep the baseline as small as possible.
1. Commit the updated baseline to complete the cleanup.

### Regenerating the Baseline
### Example

To regenerate the entire baseline from scratch:
```ruby
# Before
logger.info(user_id: current_user.id)

```bash
rm .labkit_logging_todo.yml
bundle exec labkit-logging init
# Run CI, then:
bundle exec labkit-logging fetch <project> <pipeline_id>
# After
logger.info(Labkit::Fields::GL_USER_ID => current_user.id)
```

## CI Behavior
### Add an offense to the baseline temporarily

If you cannot fix an offense immediately, add it to the baseline:

### Baseline Generation Mode
```shell
LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec
```

When `skip_ci_failure: true` is set in the todo file:
Commit the updated `.labkit_logging_todo.yml` with your changes.
Justify in your MR why you cannot fix the offense immediately.

- CI passes even when deprecated fields are detected
- Offenses are logged for collection via `labkit-logging fetch`
- Use this mode only during initial setup
## CI behavior

### Enforcement Mode
When `skip_ci_failure: true` is set in the todo file, CI passes even when
deprecated fields are detected.
Offenses are logged for collection with `labkit-logging fetch`.
Use this mode only during initial setup or recreating the todo file.

When `skip_ci_failure` is not set (normal operation):

- **New offenses fail the pipeline** with a detailed error message
- **Known offenses** (in the baseline) are allowed
- **Fixed offenses** are automatically detected and can be removed from the baseline
- New offenses fail the pipeline.
- Known offenses in the baseline are allowed.
- Fixed offenses are detected automatically.

Example CI failure output:

```
```plaintext
================================================================================
LabKit Logging Field Standardization: New Offenses Detected
================================================================================
@@ -139,93 +177,75 @@ app/models/project.rb:15: 'project_id' is deprecated. Use 'Labkit::Fields::GL_PR

================================================================================
Total: 2 new offense(s) in 2 file(s)
================================================================================
```

### When Offenses Are Fixed

When you fix offenses that were in the baseline, you'll see a message indicating which offenses were resolved. Update the baseline locally to remove them:

```bash
LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec
git add .labkit_logging_todo.yml
git commit -m "Remove fixed logging offenses from baseline"
See https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md#new-offenses-detected
================================================================================
```

## CLI Reference

The `labkit-logging` command provides subcommands for managing the field validator.
## CLI reference

```bash
```shell
bundle exec labkit-logging <command> [options]
```

### labkit-logging init
### `labkit-logging init`

Creates a new `.labkit_logging_todo.yml` file with `skip_ci_failure: true`.
Creates `.labkit_logging_todo.yml` with `skip_ci_failure: true`.

```bash
bundle exec labkit-logging init
```

### labkit-logging fetch
### `labkit-logging fetch`

Fetches offense logs from a GitLab CI pipeline and updates the todo file.

```bash
```shell
bundle exec labkit-logging fetch <project> <pipeline_id>
```

**Arguments:**
- `project` - GitLab project ID or path (e.g., `278964` or `gitlab-org/gitlab`)
- `pipeline_id` - CI pipeline ID number
- `project` - GitLab project ID or path (for example, `278964` or
  `gitlab-org/gitlab`).
- `pipeline_id` - CI pipeline ID number.

**Environment Variables:**
- `GITLAB_TOKEN` - GitLab API token (required)
- `CI_API_V4_URL` - GitLab API URL (default: `https://gitlab.com/api/v4`)
### Environment variables

**Examples:**
| Variable                          | Description                                               |
|-----------------------------------|-----------------------------------------------------------|
| `LABKIT_LOGGING_TODO_UPDATE=true` | Update the baseline with new offenses (local development) |
| `GITLAB_TOKEN`                    | GitLab API token for `labkit-logging fetch` (required)    |
| `CI_API_V4_URL`                   | GitLab API URL (default: `https://gitlab.com/api/v4`)     |

```bash
# Using project path
bundle exec labkit-logging fetch gitlab-org/gitlab 12345
## Troubleshooting

# Using project ID
bundle exec labkit-logging fetch 278964 12345
```
### New offenses detected

## Environment Variables
The validator raises an error when code introduces a deprecated logging field
that is not in the baseline.
This can happen when you:

| Variable | Description |
|----------|-------------|
| `LABKIT_LOGGING_TODO_UPDATE=true` | Update the baseline with new offenses (local development) |
| `GITLAB_TOKEN` | GitLab API token for fetching CI logs |
| `CI_API_V4_URL` | GitLab API URL (defaults to gitlab.com) |
- Add a new log call that uses a deprecated field name.
- Update `labkit-ruby` to a version that deprecates a field your code uses.

## Troubleshooting
To resolve, either [fix the offense](#how-to-fix-an-offense) or
[add it to the baseline temporarily](#add-an-offense-to-the-baseline-temporarily).

### Offenses not detected

- Ensure `.labkit_logging_todo.yml` exists in your project root.
- Verify you are using `Labkit::Logging::JsonLogger`.
- Check you are logging with a Hash (not String).
- Verify the code path is executed during tests.

**"New Offenses Detected" in CI**
- Fix the deprecated fields in your code, or
- Update the baseline locally: `LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec`
- Commit the updated `.labkit_logging_todo.yml`
### Pipeline not found when fetching

**Offenses not detected**
- Ensure `.labkit_logging_todo.yml` exists in your project root
- Verify you're using `Labkit::Logging::JsonLogger`
- Check you're logging with a Hash (not String)
- Verify the code path is executed during tests
- Verify the project path or ID is correct.
- Ensure the pipeline has completed (not still running).
- Check your `GITLAB_TOKEN` has read access to the project.

**Pipeline not found when fetching**
- Verify the project path/ID is correct
- Ensure the pipeline has completed (not still running)
- Check your `GITLAB_TOKEN` has read access to the project
### No offenses found in pipeline

**No offenses found in pipeline**
- Ensure `skip_ci_failure: true` was set during the CI run
- Verify the pipeline ran tests that exercise the logging code
- Check job logs are accessible with your token
- Ensure `skip_ci_failure: true` was set during the CI run.
- Verify the pipeline ran tests that exercise the logging code.
- Check job logs are accessible with your token.

## TODO File Format
## TODO file format

```yaml
# LabKit Logging Field Standardization TODO
+1 −1
Original line number Diff line number Diff line
@@ -158,7 +158,7 @@ module Labkit
          lines << ("=" * 80)
          lines << "Total: #{new_offenses.size} new offense(s) in #{new_offenses.map { |o| o['callsite'] }.uniq.size} file(s)" # rubocop:disable Rails/Pluck
          lines << ""
          lines << "See https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md"
          lines << "See https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md#new-offenses-detected"
          lines << ("=" * 80)
          lines << ""

+4 −17
Original line number Diff line number Diff line
@@ -88,25 +88,12 @@ module Labkit
              # This file tracks deprecated logging fields that need to be migrated to standard fields.
              # Each offense represents a file using a deprecated field that should be replaced.
              #
              # === HOW TO FIX ===
              # How to fix: https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md#how-to-fix-an-offense
              #
              # 1. Replace the deprecated field with the standard field constant
              # 2. Remove the deprecated field entirely (adding the new field is not enough)
              # 3. Run your tests - the offense will be automatically removed
              #
              # Example:
              #   # Before
              #   logger.info(user_id: 123)
              #
              #   # After
              #   logger.info(Labkit::Fields::GL_USER_ID => 123)
              #
              # === ADDING OFFENSES (if fixing is not immediately possible) ===
              #
              # Run: LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec <spec_file>
              #
              # === REGENERATE ENTIRE TODO ===
              # Adding offenses (if fixing is not immediately possible):
              #   LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec <spec_file>
              #
              # Regenerate entire TODO:
              #   Delete this file and run: LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec

            HEADER
+2 −2
Original line number Diff line number Diff line
@@ -226,8 +226,8 @@ RSpec.describe Labkit::Logging::FieldValidator::Config do

      expect(header).to include('LabKit Logging Field Standardization TODO')
      expect(header).to include('AUTO-GENERATED FILE')
      expect(header).to include('HOW TO FIX')
      expect(header).to include('Labkit::Fields::GL_USER_ID')
      expect(header).to include('How to fix:')
      expect(header).to include('FIELD_STANDARDIZATION.md#how-to-fix-an-offense')
    end
  end
end