/* * Copyright 2016-present Facebook, Inc. * * 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.facebook.buck.util.network; import com.facebook.buck.distributed.thrift.FrontendRequest; import com.facebook.buck.distributed.thrift.FrontendRequestType; import com.facebook.buck.distributed.thrift.FrontendResponse; import com.facebook.buck.distributed.thrift.LogRequest; import com.facebook.buck.distributed.thrift.LogRequestType; import com.facebook.buck.distributed.thrift.ScribeData; import com.facebook.buck.log.Logger; import com.facebook.buck.slb.ThriftService; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.io.IOException; import java.util.concurrent.Callable; public class ThriftScribeLogger extends ScribeLogger { private static final Logger LOG = Logger.get(ThriftScribeLogger.class); private final ThriftService<FrontendRequest, FrontendResponse> thriftService; private final ListeningExecutorService executorService; public ThriftScribeLogger( ThriftService<FrontendRequest, FrontendResponse> thriftService, ListeningExecutorService executorService) { this.thriftService = thriftService; this.executorService = executorService; } @Override public ListenableFuture<Void> log(final String category, final Iterable<String> lines) { synchronized (executorService) { if (executorService.isShutdown()) { String errorMessage = String.format( "%s will not accept any more log calls because it has already been closed.", getClass()); LOG.warn(errorMessage); return Futures.immediateFailedCheckedFuture(new IllegalStateException(errorMessage)); } } return executorService.submit( new Callable<Void>() { @Override public Void call() throws Exception { sendViaThrift(category, lines); return null; } }); } private void sendViaThrift(String category, Iterable<String> lines) throws IOException { //Prepare log request. ScribeData scribeData = new ScribeData(); scribeData.setCategory(category); copyLinesWithoutNulls(lines, scribeData); LogRequest logRequest = new LogRequest(); logRequest.setType(LogRequestType.SCRIBE_DATA); logRequest.setScribeData(scribeData); // Wrap in high-level request & response. FrontendRequest request = new FrontendRequest(); request.setType(FrontendRequestType.LOG); request.setLogRequest(logRequest); FrontendResponse response = new FrontendResponse(); thriftService.makeRequest(request, response); if (!response.isWasSuccessful()) { throw new IOException( String.format("Log request failed. Error from response: %s", response.getErrorMessage())); } } @VisibleForTesting static void copyLinesWithoutNulls(Iterable<String> lines, ScribeData scribeData) { int numberOfNullLines = 0; int totalLines = 0; for (String line : lines) { ++totalLines; if (line != null) { scribeData.addToLines(line); } else { ++numberOfNullLines; } } // TODO(ruibm): This way we get some signal where the null lines are coming from and still send // back as much non-corrupted data as we can. if (numberOfNullLines > 0) { LOG.error( String.format( "Out of [%d] log lines, [%d] were null for category [%s].", totalLines, numberOfNullLines, scribeData.getCategory())); } } @Override public void close() throws IOException { synchronized (executorService) { executorService.shutdown(); } thriftService.close(); } }