Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kube Logs are stored and retrieved from Cloud Storage. #4053

Merged
merged 44 commits into from
Jun 19, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a0827fa
Set Log4j2 debug mode. Successfull route job and app logs to S3 while…
davinchia Jun 11, 2021
814449f
Turn everything into environment variables. Successfully use property…
davinchia Jun 13, 2021
aafa5bd
Checkpoint: Get GCS working.
davinchia Jun 14, 2021
d4e611f
Checkpoint: Get all objects out of a directory and write it to a file.
davinchia Jun 15, 2021
ff959a3
Checkpoint: Get scheduler and server logs from Cloud working using th…
davinchia Jun 16, 2021
09c6d1d
Setup to test S3LoggingClient.
davinchia Jun 16, 2021
98bca11
One initial test in S3Logs class.
davinchia Jun 16, 2021
329bfd1
Fill in normal test.
davinchia Jun 16, 2021
7d97575
Look at a specific folder.
davinchia Jun 16, 2021
e9bd71d
Correct error.
davinchia Jun 16, 2021
35d1abf
Get logs tail working.
davinchia Jun 16, 2021
6415d55
Format.
davinchia Jun 16, 2021
3ff34b7
Pass test.
davinchia Jun 16, 2021
c9e89cb
Make logging test script executable. Disable most of build for faster…
davinchia Jun 16, 2021
0299f44
Mount S3 secret for logging test.
davinchia Jun 16, 2021
746a593
Strip quotes from jq output.
davinchia Jun 16, 2021
b3320c3
Uncomment rest of build.
davinchia Jun 16, 2021
23c8c93
Add comments.
davinchia Jun 16, 2021
afbe9d2
Checkpoint: Kube modifications. Scheduler, Server and Job logs are wo…
davinchia Jun 17, 2021
19db827
Remove affinity.
davinchia Jun 17, 2021
858090b
Revert "Remove affinity."
davinchia Jun 17, 2021
0dcb92b
Checkpoint: Stop emitting job id with MDC as we are not actually usin…
davinchia Jun 17, 2021
742d780
Checkpoint: Remove Temporal PVC. Clean up logging functions and MDC key.
davinchia Jun 17, 2021
d08269a
Merge remote-tracking branch 'origin' into davinchia/s3-logging-exper…
davinchia Jun 18, 2021
06f6981
Push code that is failing on Davin machine so Jared can test.
davinchia Jun 18, 2021
cbca608
Remove random changes.
davinchia Jun 18, 2021
e259fa3
Remove unused plugin.
davinchia Jun 18, 2021
9801789
Remove GCP configuration for now.
davinchia Jun 18, 2021
e062f71
Remove logic added for GKE bug.
davinchia Jun 18, 2021
af615e1
Checkpoint: Job and app logs work. Get S3LogClient integration test w…
davinchia Jun 18, 2021
0d8ccbc
Fix worker log test.
davinchia Jun 18, 2021
35e998a
Fix job scheduler test.
davinchia Jun 18, 2021
ede5e42
Correct cloud storage test path. Fix format.
davinchia Jun 18, 2021
2cb5730
Stash to check if master acceptance tests work.
davinchia Jun 18, 2021
b49c7c9
Does registering all modules fix test?
davinchia Jun 18, 2021
8be0a6e
Add S3 credentials information to Kube acceptance tests.
davinchia Jun 18, 2021
786d594
Add comments to explain Log4j config.
davinchia Jun 18, 2021
9858624
Remove unneeded package.
davinchia Jun 18, 2021
d0043b3
Remove unneeded comments.
davinchia Jun 18, 2021
31dd280
Respond to PR feedback.
davinchia Jun 19, 2021
7014ea3
Explicitly require Jackson date time module.
davinchia Jun 19, 2021
d6b949d
Format.
davinchia Jun 19, 2021
22eb741
Migrate all mappers to use injected function.
davinchia Jun 19, 2021
2c4bd4c
More format.
davinchia Jun 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,16 @@ WEBAPP_URL=http://localhost:8000/
API_URL=/api/v1/
TEMPORAL_HOST=airbyte-temporal:7233
INTERNAL_API_HOST=airbyte-server:8001

# Fill the below variables to log to cloud storage. The provided credentials require both S3 read/write permissions. The logger attempts to create the bucket if
davinchia marked this conversation as resolved.
Show resolved Hide resolved
# it does not exist.
# S3
S3_LOG_BUCKET=
S3_LOG_BUCKET_REGION=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

# The point to a file that is mounted via volumes. The most convenient is to place this in LOCAL_ROOT.
# GCS
GCP_STORAGE_BUCKET=
GOOGLE_APPLICATION_CREDENTIALS=/tmp/airbyte_local/credentials.json
5 changes: 5 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ jobs:
- name: Build
run: CORE_ONLY=true ./gradlew --no-daemon build --scan

- name: Run Logging Tests
davinchia marked this conversation as resolved.
Show resolved Hide resolved
run: ./tools/bin/logging_test.sh
env:
AWS_S3_INTEGRATION_TEST_CREDS: ${{ secrets.AWS_S3_INTEGRATION_TEST_CREDS }}

- name: Ensure no file change
run: git status --porcelain && test -z "$(git status --porcelain)"

Expand Down
3 changes: 1 addition & 2 deletions airbyte-commons/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ plugins {
}

dependencies {
implementation 'org.apache.commons:commons-compress:1.20'
implementation 'org.apache.commons:commons-lang3:3.11'
// Dependencies for this module should be specified in the top-level build.gradle.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of https://github.com/airbytehq/airbyte/blob/master/build.gradle#L181, if everything is depending on airbyte-commons, it seems cleaner to have all of common's dependencies in the build.gradle as well.

}
60 changes: 57 additions & 3 deletions airbyte-commons/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -1,28 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Configuration status="TRACE">
<!--<Configuration status="INFO">-->
<Properties>
<Property name="default-pattern">%d{yyyy-MM-dd HH:mm:ss}{GMT+0} %highlight{%p} %C{1.}(%M):%L - %X - %m%n</Property>
<Property name="default-worker-file-pattern">%d{yyyy-MM-dd HH:mm:ss}{GMT+0} %p (%X{job_root}) %C{1}(%M):%L - %m%n</Property>

<Property name="s3-bucket">$${env:S3_LOG_BUCKET}</Property>
<Property name="s3-region">$${env:S3_LOG_BUCKET_REGION}</Property>
<Property name="s3-aws-key">$${env:AWS_ACCESS_KEY_ID}</Property>
<Property name="s3-aws-secret">$${env:AWS_SECRET_ACCESS_KEY}</Property>

<Property name="gcp-storage-bucket">$${env:GCP_STORAGE_BUCKET}</Property>

</Properties>

<Appenders>
<Console name="Default" target="SYSTEM_OUT">
<PatternLayout pattern="${default-pattern}"/>
</Console>

<Routing name="LogSplit">
<Routes pattern="$${ctx:job_root}">
<!-- Don't split logs if job_root isn't defined -->
<Route key="$${ctx:job_root}">
<Null name="/dev/null"/>
</Route>
<Route>
<File name="${ctx:job_root}" fileName="${ctx:job_root}/${ctx:job_log_filename}">
<File name="${ctx:job_root}-local" fileName="${ctx:job_root}/${ctx:job_log_filename}">
<PatternLayout pattern="${default-worker-file-pattern}"/>
</File>
</Route>
</Routes>
<IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
</Routing>
<Routing name="LogSplitCloud">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will comment why we had to configure in this way - essentially only accepts one appender.

<Routes pattern="$${ctx:job_root}">
<!-- Don't split logs if job_root isn't defined -->
<Route key="$${ctx:job_root}">
<Null name="/dev/null"/>
</Route>
<Route>
<Log4j2Appender name="${ctx:job_root}-cloud"
verbose="true"
stagingBufferAge="1"
s3Bucket="${s3-bucket}" s3Path="job-logging${ctx:job_root}/${ctx:job_log_filename}" s3Region="${s3-region}"
s3AwsKey="${s3-aws-key}" s3AwsSecret="${s3-aws-secret}"
gcpStorageBucket="${gcp-storage-bucket}" gcpStorageBlobNamePrefix="job-logging${ctx:job_root}/${ctx:job_log_filename}">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</Pattern>
</PatternLayout>
</Log4j2Appender>
</Route>
</Routes>
<IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
</Routing>

<Routing name="AppLogSplit">
<Routes pattern="$${ctx:workspace_app_root}">
<!-- Don't split logs if workspace_app_log_root isn't defined -->
Expand All @@ -31,7 +63,7 @@
</Route>
<Route>
<RollingFile
name="${ctx:workspace_app_root}"
name="${ctx:workspace_app_root}-local"
fileName="${ctx:workspace_app_root}/logs.log"
filePattern="${ctx:workspace_app_root}/logs.%i.log.gz"
ignoreExceptions="false">
Expand All @@ -47,13 +79,35 @@
</Routes>
<IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
</Routing>
<Routing name="AppLogSplitCloud">
<Routes pattern="$${ctx:workspace_app_root}">
<!-- Don't split logs if workspace_app_log_root isn't defined -->
<Route key="$${ctx:workspace_app_root}">
<Null name="/dev/null"/>
</Route>
<Route>
<Log4j2Appender name="app-logging/${ctx:workspace_app_root}-cloud/"
stagingBufferAge="1"
s3Bucket="${s3-bucket}" s3Path="app-logging${ctx:workspace_app_root}" s3Region="${s3-region}"
s3AwsKey="${s3-aws-key}" s3AwsSecret="${s3-aws-secret}"
gcpStorageBucket="${gcp-storage-bucket}" gcpStorageBlobNamePrefix="app-logging${ctx:workspace_app_root}">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</Pattern>
</PatternLayout>
</Log4j2Appender>
</Route>
</Routes>
<IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
</Routing>
</Appenders>

<Loggers>
<Root level="INFO">
<AppenderRef ref="Default"/>
<AppenderRef ref="LogSplit"/>
<AppenderRef ref="LogSplitCloud"/>
<AppenderRef ref="AppLogSplit"/>
<AppenderRef ref="AppLogSplitCloud"/>
</Root>

<Logger name="org.eclipse.jetty" level="INFO" />
Expand Down
4 changes: 4 additions & 0 deletions airbyte-config/models/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import org.jsonschema2pojo.SourceType

plugins {
id 'airbyte-docker'
davinchia marked this conversation as resolved.
Show resolved Hide resolved
id 'airbyte-integration-test-java'
id "com.github.eirnym.js2p" version "1.0"
}

dependencies {
implementation project(':airbyte-json-validation')
implementation project(':airbyte-protocol:models')

integrationTestJavaImplementation project(':airbyte-config:models')
}

jsonSchema2Pojo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ public interface Configs {

Set<Integer> getTemporalWorkerPorts();

// The following methods retrieve logging related information.
String getS3LogBucket();

String getS3LogBucketRegion();

String getAwsAccessKey();

String getAwsSecretAccessKey();

enum TrackingStrategy {
SEGMENT,
LOGGING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package io.airbyte.config;

import com.google.common.base.Preconditions;
import io.airbyte.config.helpers.LogHelpers;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
Expand Down Expand Up @@ -177,6 +178,26 @@ public Set<Integer> getTemporalWorkerPorts() {
.collect(Collectors.toSet());
}

@Override
public String getS3LogBucket() {
davinchia marked this conversation as resolved.
Show resolved Hide resolved
return getEnsureEnv(LogHelpers.S3_LOG_BUCKET);
}

@Override
public String getS3LogBucketRegion() {
return getEnsureEnv(LogHelpers.S3_LOG_BUCKET_REGION);
}

@Override
public String getAwsAccessKey() {
return getEnsureEnv(LogHelpers.AWS_ACCESS_KEY_ID);
}

@Override
public String getAwsSecretAccessKey() {
return getEnsureEnv(LogHelpers.AWS_SECRET_ACCESS_KEY);
}

private String getEnvOrDefault(String key, String defaultValue) {
return getEnvOrDefault(key, defaultValue, Function.identity());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* MIT License
*
* Copyright (c) 2020 Airbyte
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.airbyte.config.helpers;

import io.airbyte.config.Configs;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
* Interface for various Cloud Storage clients supporting Cloud log retrieval.
*
* The underlying assumption 1) each file at the path is part of the entire log file represented by that path 2) log files names start with timestamps,
* making it possible extract the time the file was written from it's name.
*/
public interface CloudLogs {

/**
* Retrieve all objects at the given path in lexicographical order, and return their contents as one file.
*/
File downloadCloudLog(Configs configs, String logPath) throws IOException;

/**
* Assume all the lexicographically ordered objects at the given path form one giant log file, return the last numLines lines.
*/
List<String> tailCloudLog(Configs configs, String logPath, int numLines) throws IOException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,33 @@

package io.airbyte.config.helpers;

import io.airbyte.commons.io.IOs;
import io.airbyte.config.Configs;
import io.airbyte.config.Configs.WorkerEnvironment;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogHelpers {

// if you update these values, you must also update log4j2.xml
private static final Logger LOGGER = LoggerFactory.getLogger(LogHelpers.class);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should all the logging related stuff be moved to it's own module? or can it stay in models for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can stay for now but should be split eventually.


private static final int LOG_TAIL_SIZE = 1000000;
private static final CloudLogs S3 = new S3Logs();
davinchia marked this conversation as resolved.
Show resolved Hide resolved

// Any changes to the following values must also be propagated to the log4j2.xml in main/resources.
public static String WORKSPACE_MDC_KEY = "workspace_app_root";
public static String LOG_FILENAME = "logs.log";
public static String S3_LOG_BUCKET = "S3_LOG_BUCKET";
public static String S3_LOG_BUCKET_REGION = "S3_LOG_BUCKET_REGION";
public static String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID";
public static String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY";

public static String APP_LOGGING_CLOUD_PREFIX = "app-logging";
public static String JOB_LOGGING_CLOUD_PREFIX = "job-logging";

public static Path getServerLogsRoot(Configs configs) {
return configs.getWorkspaceRoot().resolve("server/logs");
Expand All @@ -43,11 +61,42 @@ public static Path getSchedulerLogsRoot(Configs configs) {
}

public static File getServerLogFile(Configs configs) {
return getServerLogsRoot(configs).resolve(LOG_FILENAME).toFile();
var logPathBase = getServerLogsRoot(configs);

if (configs.getWorkerEnvironment().equals(WorkerEnvironment.DOCKER)) {
return logPathBase.resolve(LOG_FILENAME).toFile();
}

var cloudLogPath = APP_LOGGING_CLOUD_PREFIX + logPathBase;
try {
return S3.downloadCloudLog(configs, cloudLogPath);
} catch (IOException e) {
throw new RuntimeException("Error retrieving log file: " + cloudLogPath + " from S3", e);
}
}

public static File getSchedulerLogFile(Configs configs) {
return getSchedulerLogsRoot(configs).resolve(LOG_FILENAME).toFile();
var logPathBase = getSchedulerLogsRoot(configs);

if (configs.getWorkerEnvironment().equals(WorkerEnvironment.DOCKER)) {
return logPathBase.resolve(LOG_FILENAME).toFile();
}

var cloudLogPath = APP_LOGGING_CLOUD_PREFIX + logPathBase;
try {
return S3.downloadCloudLog(configs, cloudLogPath);
} catch (IOException e) {
throw new RuntimeException("Error retrieving log file: " + cloudLogPath + " from S3", e);
}
}

public static List<String> getJobLogFile(Configs configs, Path logPath) throws IOException {
if (configs.getWorkerEnvironment().equals(WorkerEnvironment.DOCKER)) {
return IOs.getTail(LOG_TAIL_SIZE, logPath);
}

var cloudLogPath = JOB_LOGGING_CLOUD_PREFIX + logPath;
return S3.tailCloudLog(configs, cloudLogPath, LOG_TAIL_SIZE);
}

}