Skip to content

Commit

Permalink
Merge pull request #4577 from rundeck/exp/thread-outputstream-core
Browse files Browse the repository at this point in the history
Refactor: move threadbound log outstream to core
  • Loading branch information
gschueler committed Mar 5, 2019
2 parents b3ad38b + e1fda57 commit 86d8324
Show file tree
Hide file tree
Showing 16 changed files with 658 additions and 351 deletions.
51 changes: 51 additions & 0 deletions core/src/main/java/com/dtolabs/rundeck/core/utils/LogBuffer.java
@@ -0,0 +1,51 @@
/*
* Copyright 2019 Rundeck, Inc. (http://rundeck.com)
*
* 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.dtolabs.rundeck.core.utils;

/**
* Buffer for capturing data into another data type
*
* @param <D> data type
*/
public interface LogBuffer<D> {
/**
* @return true if empty
*/
boolean isEmpty();

/**
* reset buffer
*/
void reset();

/**
* write a byte
*
* @param b data
*/
void write(byte b);

/**
* Clear the buffer
*/
void clear();

/**
* @return result datatype
*/
D get();
}
@@ -0,0 +1,42 @@
/*
* Copyright 2019 Rundeck, Inc. (http://rundeck.com)
*
* 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.dtolabs.rundeck.core.utils;

import java.nio.charset.Charset;
import java.util.function.Consumer;

/**
* Manager for log buffers for a data type
*
* @param <D> datatype
* @param <T> log buffer type
*/
public interface LogBufferManager<D, T extends LogBuffer<D>> {
/**
* Create a new log buffer with the charset
*
* @param charset charset
*/
T create(Charset charset);

/**
* Flush all buffers with the consumer
*
* @param writer consumer of log events
*/
void flush(Consumer<D> writer);
}
@@ -0,0 +1,62 @@
/*
* Copyright 2019 Rundeck, Inc. (http://rundeck.com)
*
* 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.dtolabs.rundeck.core.utils;

import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;

/**
* Implements basic buffer of data into a String
*/
public class StringLogBuffer
implements LogBuffer<String>
{
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final Charset charset;

public StringLogBuffer(final Charset charset) {
this.charset = charset;
}

@Override
public boolean isEmpty() {
return baos.size() == 0;
}

@Override
public void reset() {
baos = new ByteArrayOutputStream();
}

@Override
public void write(final byte b) {
baos.write(b);
}

@Override
public void clear() {
reset();
}

/**
* @return contents as string
*/
public String get() {
return charset != null ? new String(baos.toByteArray(), charset) : new String(baos.toByteArray());
}

}
@@ -0,0 +1,57 @@
/*
* Copyright 2019 Rundeck, Inc. (http://rundeck.com)
*
* 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.dtolabs.rundeck.core.utils;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
* Managers string log buffers
*/
public class StringLogManager
implements LogBufferManager<String, StringLogBuffer>

{
private List<StringLogBuffer> buffers = new ArrayList<>();
private Charset charset;

public StringLogManager(final Charset charset) {
this.charset = charset;
}

@Override
public StringLogBuffer create(final Charset charset) {
StringLogBuffer buffer = new StringLogBuffer(charset != null ? charset : this.charset);
buffers.add(buffer);
return buffer;
}

@Override
public void flush(final Consumer<String> writer) {
for (StringLogBuffer buffer : buffers) {
writer.accept(buffer.get());
buffer.clear();
}
buffers.clear();
}

public static StringLogManager factory(Charset charset) {
return new StringLogManager(charset);
}
}
@@ -0,0 +1,171 @@
/*
* Copyright 2016 SimplifyOps, Inc. (http://simplifyops.com)
*
* 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.dtolabs.rundeck.core.utils;

import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.function.Consumer;
import java.util.function.Function;

/**
* Thread local buffered log output
* 解决rundeck中文log乱码的问题,此文件在rundeck源文件路径:./rundeckapp/src/groovy/com/dtolabs/rundeck/app/internal/logging/ThreadBoundLogOutputStream.groovy
* yeml#ucweb.com
*/
public class ThreadBoundLogOutputStream<D, T extends LogBuffer<D>>
extends OutputStream
{
private Consumer<D> logger;
private ThreadLocal<Holder<T>> buffer = new ThreadLocal<>();
private InheritableThreadLocal<LogBufferManager<D,T>> manager = new InheritableThreadLocal<>();
private InheritableThreadLocal<Charset> charset = new InheritableThreadLocal<>();
private Function<Charset, LogBufferManager<D,T>> factory;

@Data
@RequiredArgsConstructor
private static class Holder<X extends LogBuffer> {
final X buffer;
boolean crchar;

public void clear() {
crchar = false;
buffer.clear();
}

public void reset() {
crchar = false;
buffer.reset();
}
}
/**
* Create a new thread local buffered stream
* @param logger logger for events
*/
public ThreadBoundLogOutputStream(
Consumer<D> logger,
Charset charset,
Function<Charset, LogBufferManager<D,T>> factory
)
{
this.logger = logger;
this.charset.set(charset);
this.factory = factory;
}
/**
* Set the charset to use
* @param charset new charset
* @return previous charset
*/
public Charset setCharset(Charset charset) {
Charset prev = this.charset.get();
this.charset.set(charset);
return prev;
}

/**
* Install a new inherited thread local buffer manager and return it
* @return manager
*/
public LogBufferManager<D,T> installManager() {
LogBufferManager<D,T> manager = factory.apply(charset.get());
this.manager.set(manager);
return manager;
}

/**
* If no manager is set, install one, otherwise return the existing one
* @return
*/
private LogBufferManager<D,T> getOrCreateManager() {
if (null == manager.get()) {
installManager();
}
return manager.get();
}

/**
* Write output
* @param b
*/
public void write(final int b) {
Holder<T> log = getOrReset();
if (b == '\n') {
flushEventBuffer();
} else if (b == '\r') {
log.setCrchar(true);
} else {
if (log.isCrchar()) {
flushEventBuffer();
resetEventBuffer();
}
log.getBuffer().write((byte) b);
}

}

/**
* get the thread's event buffer, reset it if it is empty
* @return
*/
private Holder<T> getOrReset() {
if (buffer.get() == null || buffer.get().getBuffer().isEmpty()) {
resetEventBuffer();
}
return buffer.get();
}

/**
* reset existing or create a new buffer with the current context
*/
private void resetEventBuffer() {
if (buffer.get() == null) {
buffer.set(new Holder<>(getOrCreateManager().create(charset.get())));
} else {
buffer.get().reset();
}
}

/**
* emit a log event for the current contents of the buffer
*/
private void flushEventBuffer() {
Holder<T> holder = buffer.get();
logger.accept(holder.getBuffer().get());
holder.clear();
}

/**
* Flush all event buffers managed by the current manager
*/
public void flushBuffers() {
getOrCreateManager().flush(logger);
}

public void flush() {
}

@Override
public void close() throws IOException {
flushBuffers();

super.close();
}
}

0 comments on commit 86d8324

Please sign in to comment.