Skip to content

Latest commit

 

History

History
140 lines (102 loc) · 5.7 KB

6.md

File metadata and controls

140 lines (102 loc) · 5.7 KB

Задание 6

Реализовать логирование.

Функциональные требования

  • Поддержать возможность структурного логирования в JSON формате (по умолчанию) и текстовом. Пользователь должен иметь возможность выбрать желаемый формат с помощью переменной окружения:

    export JSON_LOG=1
  • Предоставить пользователю возможность задавать уровни логирования (info (по умолчанию), error, debug и пр.). Пользователь должен иметь возможность задать уровень логирования с помощью переменной окружения:

    export LOG_LEVEL=error
  • Реализовать middleware для логирования всех входящих запросов и результатов их выполнения (HTTP код, текст ошибки).

  • Реализовать перехват и восстановление (recover) после паники на уровне HTTP API с учетом ситуаций, когда восстановление невозможно. Сервис должен логировать информацию по перехваченной панике.

Нефункциональные требования

  • Для реализации логирования применить slog из стандартной библиотеки Golang.

Обработка паники

Паника в языке Go во многом похожа на исключения в других языках программирования. Однако ее не следует использовать как быстрый способ передать ошибку на более высокий уровень. Паники обычно указывает на критическую ошибку, которая делает продолжением работы сервиса невозможным (чем-то похоже на функцию assert из С++).

В каких случаях имеет смысл паниковать? Рассмотрим несколько допустимых сценариев (этот список не полный, но позволяет продемонстрировать идею):

  • Мисконфигурация по вине пользователя: Сервис читает конфигурацию на старте и обнаруживает, что она содержит недопустимые значения.

  • Грубая ошибка разработчика: Возникла ситуация, которая явно указывает на ошибку в коде, например:

    func Wrap(extra string, err error) error {
        if err == nil {
            panic("wrapping nil error")
        }
    
        return fmt.Errorf("%s: %w", extra, err)
    }
  • Непредвиденная ошибка во время подготовки окружения для юнит- или интеграционных тестов:

    func (s *SmokeTestSuite) SetupTest() {
        conn, err := grpc.NewClient(s.GRPCAddress,
            grpc.WithTransportCredentials(insecure.NewCredentials()),
        )
        if err != nil {
            panic(err)
        }
    
        s.Conn = conn
    }
  • Паника в коде сторонних библиотек: В некоторых случаях паника может возникнуть из-за ошибок в сторонних библиотеках.

Для повышения стабильности работы сервиса разработчики часто реализуют специальную middleware функцию, который перехватывает панику при обработке запросов и пытается вернуть программу в работоспособное состояние, записывая ошибку в лог. Именно такую функцию и необходимо реализовать в задании.

К сожалению в ряде случаев восстановление после паники невозможно, один из таких сценариев проиллюстрирован в коде ниже:

package main

import "fmt"

func doWork(i int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from", r)
        }
    }()

    if i == 2 {
        panic("Kaboom!")
    }

    fmt.Println("Doing something useful with", i)
}

func unrecoverablePanic() {
    var f func()
    go f()
}

func main() {
    for range 3 {
        doWork(1)
        doWork(2)
       doWork(3)
    }

    // Raise unrecoverable panic.
    unrecoverablePanic()

    // Will never get here.
    doWork(1)
}

Материалы для ознакомления