Как «следить» за отдельной транзакцией в логах (Java EE) - PullRequest
2 голосов
/ 29 марта 2012

У нас есть корпоративное приложение (развернутое как ухо), представляющее собой пакетный процессор, который создает несколько потоков для одновременной работы с элементами пакета (до максимального числа одновременных потоков). В рамках обработки он вызывает несколько веб-сервисов RESTful, развернутых в одной и той же Glassfish, но в двух доменах. Каждое приложение регистрирует свой собственный файл журнала приложения на сервере, и все журналы также поступают на сервер Splunk.

Нас попросили найти способ "пометить" каждый оператор журнала, чтобы мы могли выполнить поиск по всем журналам и получить все операторы, связанные с обработкой одного элемента пакета.

Каждый обрабатываемый элемент имеет уникальный идентификатор, поэтому добавить его в инструкции журнала процессора легко. Вопрос в том, как пометить журналы с веб-сервисов?

Одним из предложений было добавить параметр для каждого вызова веб-службы, который принимает этот идентификатор и распространяет его на каждый метод этих служб. Я думаю, что это сумасшедшая идея - вносить изменения такого типа в код, чтобы передать значение просто для регистрации.

Кто-нибудь сделал что-нибудь подобное? Любые предложения о том, как определить записи журнала, которые "принадлежат друг другу"?

Кстати, мы используем slf4j с log4j, но рассматриваем возможность использования logback.

UPDATE

Я работал над передачей идентификатора транзакции в качестве заголовка HTTP, но не могу заставить его работать. Я использую Джерси для себя и вот что у меня есть:

Класс My Processor, который помещает свое значение в MDC ведения журнала в качестве TransactionId и использует клиент REST-службы для некоторой обработки:

public Processor
{
    RESTClient myRESTClient = new RESTClient("http://path/to/restService");

    public void process(final Object object)
    {
        //Put the object ID in the logging MDC
        log.debug("Putting '{}' in the MDC as the {} header value.",  object.getObjectID(), "transactionID");
        MDC.put("transactionID", object.getObjectID());

    //Do some stuff

    Object anotherObject = myRESTClient.doQuery(object.getValue());

    //Do more some stuff
    }
}

Мой RESTClient для доступа к службе REST. Именно здесь я извлекаю идентификатор транзакции из MDC и добавляю его в качестве заголовка к запросу:

public RESTClient
{
    public Object doQuery(String value)
    {
        Object object = null;

        try
        {
            Builder builder = myRestService.queryParam(PARAM_KEY_VALUE, value)
                    .accept(MediaType.APPLICATION_XML);

            String transactionId = (String) MDC.get("transactionID");

            logger.debug("Retrieved '{}' from MDC for key {}",
                transactionId,
                "transactionID");

            if (this.getTransactionID() != null)
            {
                builder = builder.header("transactionID", transactionId);
            }

            object = builder.get(Object.class);
        }
        catch (Throwable ex)
        {
            //Do error handling
        }
    }
}

Мой класс ресурсов службы REST, который должен иметь идентификатор транзакции в своих заголовках запроса и помещает его в свой журнал MDC:

@Path("/myPath")
public class MyResource
{
    @Context
    private HttpContext httpContext;

    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Object doQuery( @QueryParam("value") String value)
    {
        putTransactionIdInMDC();

        SubscriberAccount account = null;

        try
        {
            //Do query stuff
        }
        catch (Exception ex)
        {
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }

        return account;
    }

    private void putTransactionIdInMDC()
    {
        if (httpContext != null)
        {
            String transactionID = httpContext.getRequest()
                    .getHeaderValue("transactionID");

            if (transactionID != null && transactionID.isEmpty())
            {
                /*
                 * It's not likely, but possible that two headers with the same
                 * header key were put in the request. If so, the value will be
                 * a comma separated list. We're using the first one.
                 */
                String[] strings = transactionID.split(",");

                logger.debug("Header '{}' value(s): {}",
                        "transactionID",
                        strings);

                MDC.put("transactionID", strings[0]);
            }
            else
            {
                logger.debug("The header '{}' was not included in the request.",
                        "transactionID");
            }
        }
        else
        {
            logger.info("Could not get an HttpContext for the request");
        }
    }
}

Основываясь на моем журнале, я знаю, что транзакционный идентификатор помещается в MDC процессора и извлекается из него классом RESTClient. Однако он не передается в качестве заголовка http в службу REST. Может кто-нибудь сказать мне, почему нет?

Файл журнала процессора:

2012-04-13T17:30:36.541 MDT  INFO [Ejb-Async-Thread-2] DEBUG my.package.Processor - Putting '12311497-2279-4516-af7d-cf9716f7748a' in the MDC as the transactionId header value.

2012-04-13T17:30:36.541 MDT  INFO [Ejb-Async-Thread-2] DEBUG my.package.RESTClient- Retrieved '12311497-2279-4516-af7d-cf9716f7748a' from MDC for key transactionId

Файл журнала службы REST:

2012 Apr 13 17:30:36,337 MDT [http-thread-pool-80(3)] DEBUG my.package.MyResource - The header 'transactionId' was not included in the request.

ОБНОВЛЕНИЕ ВТОРОЕ

Нашел логическую ошибку в моем коде выше:

if (transactionID != null && transactionID.isEmpty())

должно было быть:

if (transactionID != null !&& transactionID.isEmpty())

Ответы [ 2 ]

1 голос
/ 31 марта 2012

Просто смутная идея: возможно, вы можете использовать конверт SOAP, чтобы «скрыть» этот надоедливый идентификатор задания от бизнес-данных. И используя некоторый перехватчик или обработчик JAX-WS на стороне клиента, он может быть установлен в запросы, не затрагивая бизнес-код. На стороне сервера другой обработчик или перехватчик может извлечь идентификатор из конверта и вставить его в материал Log4J, используя NDC (вложенный диагностический контекст) или MDC (отображенный диагностический контекст). Затем необходимо настроить форматы журналов, чтобы они действительно регистрировали значения NDC / MDC.

0 голосов
/ 16 апреля 2012

Если вы выполняете Splunking все файлы журналов со всех серверов, участвующих в пакетной обработке, и у вас есть уникальный идентификатор для работы, то в Splunk очень просто сопоставить события, составляющие каждую отдельную партию, объединяются.

Вы захотите взглянуть на Splunk Транзакция Команда поиска

Что касается добавления уникального идентификатора в журналы веб-службы, как уже упоминалось, заголовок HTTP, вероятно, является наименее инвазивным способом. И я не думаю, что это безумие, подумайте об уровнях операционной видимости, которые вы теперь у вас под рукой:)

Вот дополнительная информация о Ведение журнала лучших практик Splunk .

...