Skip to content

Commit

Permalink
Enable streaming BuckEvents to a remote entity.
Browse files Browse the repository at this point in the history
Summary:
This adds an EventListener that serializes everything it sees to
Json and uses a configurable backend to stream the data.
The EventListener will give up after a couple of tries (assuming
the network has gone down).

Test Plan:
Added integration test, checked manually:
1. Configure a project with remote_log_upload_uri = http://localhost:5555
2. nc -l 5555
3. run buck test --all
4. Objserve JSON in netcat output.
  • Loading branch information
mkosiba authored and sdwilsh committed Jul 21, 2015
1 parent 0a1d56c commit f15ddd0
Show file tree
Hide file tree
Showing 39 changed files with 1,321 additions and 228 deletions.
14 changes: 14 additions & 0 deletions docs/concept/buckconfig.soy
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,20 @@ inspection.

<p>Will compress the traces with GZIP.</p>

{literal}<pre class="prettyprint lang-ini">
[log]
remote_log_url = http://all.your.logs:500/are/belong/to/us

</pre>{/literal}

<p>Will stream the build logs to the indicated address. Currently{sp}
<code>http</code> and <code>https</code> scheme URLs are supported.
The events are sent in batches using PUT requests. Each batch contains a
JSON array composing of the long entries.</p>

<p>The format of the individual log entries is not yet finalized and may
change without warning between Buck versions.</p>

{call .section}{param title: 'ndk' /}{/call}

This section defines properties to configure building native code against
Expand Down
2 changes: 1 addition & 1 deletion src/com/facebook/buck/cli/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ java_immutables_library(
'//src/com/facebook/buck/util:escaper',
'//src/com/facebook/buck/util:exceptions',
'//src/com/facebook/buck/util:io',
'//src/com/facebook/buck/util:network',
'//src/com/facebook/buck/util:util',
'//src/com/facebook/buck/util:watchman',
'//src/com/facebook/buck/util/unit:unit',
'//src/com/facebook/buck/util/concurrent:concurrent',
'//src/com/facebook/buck/util/environment:environment',
'//src/com/facebook/buck/util/environment:env-filter',
'//src/com/facebook/buck/util/environment:platform',
'//src/com/facebook/buck/util/network:network',
'//src/com/facebook/buck/timing:timing',
'//third-party/java/args4j:args4j',
'//third-party/java/ddmlib:ddmlib',
Expand Down
12 changes: 12 additions & 0 deletions src/com/facebook/buck/cli/BuckConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import java.io.Reader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Path;
Expand Down Expand Up @@ -118,6 +119,13 @@ public class BuckConfig {
private static final String DEFAULT_HTTP_CACHE_MODE = CacheMode.readwrite.name();
private static final String DEFAULT_HTTP_CACHE_TIMEOUT_SECONDS = "3";

private static final Function<String, URI> TO_URI = new Function<String, URI>() {
@Override
public URI apply(String input) {
return URI.create(input);
}
};

private final Config config;

private final ImmutableMap<String, BuildTarget> aliasToBuildTargetMap;
Expand Down Expand Up @@ -707,6 +715,10 @@ private boolean readCacheMode(String fieldName, String defaultValue) {
return doStore;
}

public Optional<URI> getRemoteLogUrl() {
return getValue("log", "remote_log_url").transform(TO_URI);
}

public Optional<String> getValue(String sectionName, String propertyName) {
return config.getValue(sectionName, propertyName);
}
Expand Down
20 changes: 20 additions & 0 deletions src/com/facebook/buck/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.facebook.buck.event.listener.FileSerializationEventBusListener;
import com.facebook.buck.event.listener.JavaUtilsLoggingBuildListener;
import com.facebook.buck.event.listener.LoggingBuildListener;
import com.facebook.buck.event.listener.RemoteLogUploaderEventListener;
import com.facebook.buck.event.listener.SimpleConsoleEventBusListener;
import com.facebook.buck.event.listener.SuperConsoleEventBusListener;
import com.facebook.buck.httpserver.WebServer;
Expand Down Expand Up @@ -61,10 +62,12 @@
import com.facebook.buck.util.ProcessManager;
import com.facebook.buck.util.ProjectFilesystemWatcher;
import com.facebook.buck.util.PropertyFinder;
import com.facebook.buck.util.network.RemoteLoggerFactory;
import com.facebook.buck.util.Verbosity;
import com.facebook.buck.util.WatchmanWatcher;
import com.facebook.buck.util.WatchmanWatcherException;
import com.facebook.buck.util.concurrent.TimeSpan;
import com.facebook.buck.util.environment.BuildEnvironmentDescription;
import com.facebook.buck.util.environment.DefaultExecutionEnvironment;
import com.facebook.buck.util.environment.EnvironmentFilter;
import com.facebook.buck.util.environment.ExecutionEnvironment;
Expand Down Expand Up @@ -92,6 +95,7 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
Expand Down Expand Up @@ -585,6 +589,7 @@ public int runMainWithExitCode(
rootRepository.getBuckConfig(),
webServer,
clock,
executionEnvironment,
console,
consoleListener,
rootRepository.getKnownBuildRuleTypes(),
Expand Down Expand Up @@ -889,6 +894,7 @@ private ImmutableList<BuckEventListener> addEventListeners(
BuckConfig config,
Optional<WebServer> webServer,
Clock clock,
ExecutionEnvironment executionEnvironment,
Console console,
AbstractConsoleEventBusListener consoleEventBusListener,
KnownBuildRuleTypes knownBuildRuleTypes,
Expand All @@ -913,6 +919,20 @@ private ImmutableList<BuckEventListener> addEventListeners(
loadListenersFromBuckConfig(eventListenersBuilder, projectFilesystem, config);


Optional<URI> remoteLogUrl = config.getRemoteLogUrl();
ImmutableMap<String, String> environmentExtraData = ImmutableMap.of();
if (remoteLogUrl.isPresent()) {
eventListenersBuilder.add(
new RemoteLogUploaderEventListener(
objectMapper,
RemoteLoggerFactory.create(remoteLogUrl.get(), objectMapper),
BuildEnvironmentDescription.of(
executionEnvironment,
config.getArtifactCacheModes(),
environmentExtraData)
));
}


JavacOptions javacOptions = new JavaBuckConfig(config)
.getDefaultJavacOptions(new ProcessExecutor(console));
Expand Down
1 change: 1 addition & 0 deletions src/com/facebook/buck/event/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ java_immutables_library(
'//src/com/facebook/buck/model:model',
'//src/com/facebook/buck/timing:timing',
'//src/com/facebook/buck/util:exceptions',
'//src/com/facebook/buck/util:util',
'//src/com/facebook/buck/util/concurrent:concurrent',
],
provided_deps = [
Expand Down
2 changes: 1 addition & 1 deletion src/com/facebook/buck/event/listener/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ java_library(
'//src/com/facebook/buck/util:constants',
'//src/com/facebook/buck/util:exceptions',
'//src/com/facebook/buck/util:io',
'//src/com/facebook/buck/util:network',
'//src/com/facebook/buck/util/network:network',
'//src/com/facebook/buck/util:util',
'//third-party/java/guava:guava',
'//third-party/java/jackson:jackson-annotations',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright 2015-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.event.listener;

import com.facebook.buck.cli.CommandEvent;
import com.facebook.buck.event.BuckEvent;
import com.facebook.buck.event.BuckEventListener;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.rules.BuildRuleEvent;
import com.facebook.buck.util.environment.BuildEnvironmentDescription;
import com.facebook.buck.util.network.BlockingHttpEndpoint;
import com.facebook.buck.util.network.RemoteLogger;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Handles uploading events to a remote log service.
*/
public class RemoteLogUploaderEventListener implements BuckEventListener {

private static final Logger LOG = Logger.get(RemoteLogUploaderEventListener.class);
@VisibleForTesting
static final int MAX_FAILURE_COUNT = 3;

private final RemoteLogger remoteLogger;
private final ObjectMapper mapper;
private final BuildEnvironmentDescription buildEnvironmentDescription;
private final Set<ListenableFuture<Void>> pendingUploads;
private final AtomicInteger failureCount = new AtomicInteger(0);
private int sentEventsCount = 0;

public RemoteLogUploaderEventListener(
ObjectMapper objectMapper,
RemoteLogger remoteLogger,
BuildEnvironmentDescription buildEnvironmentDescription) {
this.mapper = objectMapper;
this.buildEnvironmentDescription = buildEnvironmentDescription;
this.remoteLogger = remoteLogger;
this.pendingUploads = new HashSet<>();
}

@Subscribe
public void buckEvent(final BuckEvent event) {
putInSink(event.getBuildId(), event);
}

private void putInSink(BuildId buildId, Object object) {
if (failureCount.get() > MAX_FAILURE_COUNT) {
return;
}
sentEventsCount++;

ObjectNode jsonNode = mapper.valueToTree(object);
jsonNode.put("@class", object.getClass().getCanonicalName());
jsonNode.put("buildId", buildId.toString());

Optional<ListenableFuture<Void>> upload = remoteLogger.log(jsonNode.toString());
if (upload.isPresent()) {
registerPendingUpload(upload.get());
}
}

private void registerPendingUpload(final ListenableFuture<Void> upload) {
synchronized (pendingUploads) {
pendingUploads.add(upload);
}
Futures.addCallback(
upload,
new FutureCallback<Void>() {
@Override
public void onSuccess(Void result) {
failureCount.set(0);
onDone();
}

@Override
public void onFailure(Throwable t) {
onDone();
LOG.info(t, "Failed uploading event to the remote log.");
if (failureCount.incrementAndGet() == MAX_FAILURE_COUNT) {
LOG.info("Maximum number of log upload failures reached, giving up.");
}
}

private void onDone() {
synchronized (pendingUploads) {
pendingUploads.remove(upload);
}
}
});
}

@Subscribe
public void commandStarted(CommandEvent.Started started) {
putInSink(
started.getBuildId(),
ImmutableMap.of(
"type", "CommandStartedAux",
"environment", buildEnvironmentDescription
)
);
}

@Subscribe
public void commandFinished(CommandEvent.Finished finished) {
putInSink(
finished.getBuildId(),
ImmutableMap.of(
"type", "CommandFinishedAux",
"environment", buildEnvironmentDescription,
"eventsCount", sentEventsCount + 1
)
);
}

@Subscribe
public void ruleStarted(BuildRuleEvent.Started started) {
putInSink(
started.getBuildId(),
ImmutableMap.of(
"type", "BuildRuleStartedAux",
"buildRule", started.getBuildRule(),
"deps", started.getBuildRule().getDeps()
));
}

@Override
public synchronized void outputTrace(BuildId buildId) throws InterruptedException {
ImmutableSet<ListenableFuture<Void>> uploads;
ListenableFuture<Void> drain = remoteLogger.close();
synchronized (pendingUploads) {
pendingUploads.add(drain);
uploads = ImmutableSet.copyOf(pendingUploads);
pendingUploads.clear();
}
try {
Futures.successfulAsList(uploads).get(
BlockingHttpEndpoint.DEFAULT_COMMON_TIMEOUT_MS * 100,
TimeUnit.MILLISECONDS);
} catch (ExecutionException|TimeoutException e) {
LOG.info(e, "Failed uploading remaining log data to remote server");
}
}
}
15 changes: 13 additions & 2 deletions src/com/facebook/buck/log/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,11 @@ public void info(String message)
* If the format string is invalid or the arguments are insufficient, an error will be logged and execution
* will continue.
*
* @param exception an exception associated with the warning being logged
* @param format a format string compatible with String.format()
* @param args arguments for the format string
*/
public void info(String format, Object... args)
public void info(@Nullable Throwable exception, String format, Object... args)
{
if (logger.isLoggable(INFO)) {
String message;
Expand All @@ -269,10 +270,20 @@ public void info(String format, Object... args)
logger.log(SEVERE, illegalFormatMessageFor("INFO", format, args), e);
message = rawMessageFor(format, args);
}
logger.info(message);
logger.log(INFO, message, exception);
}
}

/**
* Logs a message at INFO level.
*
* @param format a format string compatible with String.format()
* @param args arguments for the format string
*/
public void info(String format, Object... args) {
info(null, format, args);
}

/**
* Logs a message at WARN level.
*
Expand Down
1 change: 0 additions & 1 deletion src/com/facebook/buck/model/Pair.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,4 @@ public int compare(Pair<FIRST, SECOND> o1, Pair<FIRST, SECOND> o2) {
}
};
}

}
6 changes: 3 additions & 3 deletions src/com/facebook/buck/rules/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,13 @@ java_immutables_library(
'//src/com/facebook/buck/test/selectors:selectors',
'//src/com/facebook/buck/timing:timing',
'//src/com/facebook/buck/util:constants',
'//src/com/facebook/buck/util/collect:collect',
'//src/com/facebook/buck/util:exceptions',
'//src/com/facebook/buck/util/hash:hash',
'//src/com/facebook/buck/util:network',
'//src/com/facebook/buck/util:util',
'//src/com/facebook/buck/util/collect:collect',
'//src/com/facebook/buck/util/hash:hash',
'//src/com/facebook/buck/util/concurrent:concurrent',
'//src/com/facebook/buck/util/environment:environment',
'//src/com/facebook/buck/util/network:network',
'//src/com/facebook/buck/zip:steps',
'//src/com/facebook/buck/zip:unzip',
'//third-party/java/commons-compress:commons-compress',
Expand Down
Loading

0 comments on commit f15ddd0

Please sign in to comment.