├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── jinoos │ └── flume │ ├── DirPattern.java │ ├── DirectoryTailParserModulable.java │ ├── DirectoryTailSource.java │ ├── FileSet.java │ ├── MultiLineParserModule.java │ ├── SingleLineParserModule.java │ └── ThreadSafeSimpleDateFormat.java └── test └── java └── com └── jinoos └── flume_ng_extends └── AppTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.jinoos.flume-ng-extends-0.0.1-SNAPSHOT 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 3 | org.eclipse.jdt.core.compiler.compliance=1.5 4 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 5 | org.eclipse.jdt.core.compiler.source=1.5 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | flume-ng-extends 2 | ================ 3 | 4 | Source of Flume NG for tailing files in a directory 5 | 6 | Configuration 7 | ============= 8 | | Property Name | Default | Description | 9 | | ------------- | :-----: | :---------- | 10 | | Channels | - | | 11 | | Type | - | com.jinoos.flume.DirectoryTailSource | 12 | | Type | - | com.jinoos.flume.DirectoryTailSource | 13 | | dirs | - | Nick of directories, it's such as list of what directories are monitored | 14 | | parser | com.jinoos.flume.SingleLineParserModule | Implemented class path of DirectoryTailParserModulable. | 15 | | dirs.NICK.path | - | Directory path | 16 | | dirs.NICK.file-pattern | - | Pattern of target file(s), ex) '^(.*)$' is for all files | 17 | 18 | For parser, there is two module classes what implemented DirectoryTailParserModulable 19 | * SingleLineParserModule 20 | * MultiLineParserModule 21 | 22 | * Example for SingleLineParserModule 23 | ``` 24 | agent.sources = dirMon 25 | agent.sources.dirMon.type = com.jinoos.flume.DirectoryTailSource 26 | agent.sources.dirMon.parser = com.jinoos.flume.SingleLineParserModule 27 | agent.sources.dirMon.dirs = tmpDir varLogDir 28 | agent.sources.dirMon.dirs.tmpDir.path = /tmp 29 | agent.sources.dirMon.dirs.tmpDir.file-pattern = ^(message)$ # /var/log/message 30 | agent.sources.dirMon.dirs.varLogDir.path = /var/log 31 | agent.sources.dirMon.dirs.varLogDir.file-pattern = ^(.*)(\.log)$ # /tmp/*.log 32 | ``` 33 | 34 | * Example for MultiLineParserModule 35 | ``` 36 | agent.sources = dirMon 37 | agent.sources.dirMon.type = com.jinoos.flume.DirectoryTailSource 38 | agent.sources.dirMon.parser = com.jinoos.flume.MultiLineParserModule 39 | # Regex pattern for first line, format "yyyy-MM-dd HH:mm:ss" 40 | agent.sources.dirMon.first-line-pattern = ^([\d]{4})-([\d]{2})-([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2}) 41 | agent.sources.dirMon.dirs = tmpDir varLogDir 42 | agent.sources.dirMon.dirs.tmpDir.path = /tmp 43 | agent.sources.dirMon.dirs.tmpDir.file-pattern = ^(message)$ # /var/log/message 44 | agent.sources.dirMon.dirs.varLogDir.path = /var/log 45 | agent.sources.dirMon.dirs.varLogDir.file-pattern = ^(.*)(\.log)$ # /tmp/*.log 46 | ``` 47 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jinoos 6 | flume-ng-extends 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | flume-ng-extends 11 | http://jinoos.com 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 3.8.1 22 | test 23 | 24 | 25 | org.apache.commons 26 | commons-vfs2 27 | 2.0 28 | 29 | 30 | org.apache.flume 31 | flume-ng-core 32 | 1.4.0 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/DirPattern.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class DirPattern { 6 | private String path; 7 | private Pattern filePattern; 8 | 9 | public String getPath() { 10 | return path; 11 | } 12 | 13 | public void setPath(String path) { 14 | this.path = path; 15 | } 16 | 17 | public Pattern getFilePattern() { 18 | return filePattern; 19 | } 20 | 21 | public void setFilePattern(Pattern filePattern) { 22 | this.filePattern = filePattern; 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/DirectoryTailParserModulable.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import org.apache.flume.conf.Configurable; 7 | 8 | /** 9 | *
10 |  * 1. ClassName : 
11 |  * 2. FileName  : DirectoyTailModulable.java
12 |  * 3. Package  : kr.co.cplanet.logcollector.flumeng.source
13 |  * 4. Comment  : 
14 |  * 5. @author  : Jinoos Lee 
15 |  * 6. 작성일   : 2013. 10. 25. 오전 12:51:16
16 |  * 
17 | */ 18 | public interface DirectoryTailParserModulable extends Configurable { 19 | public void flush(); 20 | 21 | public void parse(String line, FileSet header); 22 | 23 | public boolean isFirstLine(String line); 24 | 25 | public boolean isLastLine(String line); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/DirectoryTailSource.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Hashtable; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.concurrent.BlockingQueue; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.Future; 12 | import java.util.concurrent.LinkedBlockingQueue; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | import java.util.regex.PatternSyntaxException; 17 | 18 | import org.apache.commons.vfs2.FileChangeEvent; 19 | import org.apache.commons.vfs2.FileListener; 20 | import org.apache.commons.vfs2.FileObject; 21 | import org.apache.commons.vfs2.FileSystemException; 22 | import org.apache.commons.vfs2.FileSystemManager; 23 | import org.apache.commons.vfs2.FileType; 24 | import org.apache.commons.vfs2.VFS; 25 | import org.apache.commons.vfs2.impl.DefaultFileMonitor; 26 | import org.apache.flume.Context; 27 | import org.apache.flume.Event; 28 | import org.apache.flume.EventDrivenSource; 29 | import org.apache.flume.conf.Configurable; 30 | import org.apache.flume.event.EventBuilder; 31 | import org.apache.flume.instrumentation.SourceCounter; 32 | import org.apache.flume.source.AbstractSource; 33 | import org.slf4j.Logger; 34 | import org.slf4j.LoggerFactory; 35 | 36 | import com.google.common.base.Preconditions; 37 | 38 | /** 39 | *
 40 |  * 1. ClassName : 
 41 |  * 2. FileName  : WasAppLogSource.java
 42 |  * 3. Package  : kr.co.cplanet.flumeng.source
 43 |  * 4. Comment  :
 44 |  * 5. 작성자   : jinoos
 45 |  * 6. 작성일   : 2013. 10. 16. 오후 9:22:44
 46 |  * 
47 | */ 48 | public class DirectoryTailSource extends AbstractSource implements 49 | Configurable, EventDrivenSource { 50 | private static final String CONFIG_SEPERATOR = "."; 51 | private static final String CONFIG_DIRS = "dirs"; 52 | private static final String CONFIG_PATH = "path"; 53 | private static final String CONFIG_FILE_PATTERN = "file-pattern"; 54 | private static final String CONFIG_PARSER = "parser"; 55 | private static final String CONFIG_KEEP_BUFFER_INTERVAL = "keep-buffer-interval"; 56 | private static final String CONFIG_FILE_MONITOR_SLEEP_TIME = "file-monitor-sleep-time"; 57 | 58 | // group(0) : DateTime YYYY-MM-DD HH:ii:ss 59 | // group(1) : Severity 60 | // group(2) : Thread Number 61 | // group(3) : Class 62 | // group(5) : Method 63 | // group(6) : line number 64 | // group(7) : Message 65 | private static final String DEFAULT_FILE_PATTERN = "^(.*)$"; 66 | 67 | private static final String DEFAULT_PARSER_MODULE_CLASS = "com.jinoos.flume.SingleLineParserModule"; 68 | 69 | private static final long DEFAULT_KEEP_BUFFER_INTERVAL = 1000; 70 | private static final long DEFAULT_FILE_MONITOR_SLEEP_TIME = 500; 71 | 72 | private static final Logger logger = LoggerFactory 73 | .getLogger(DirectoryTailSource.class); 74 | 75 | private SourceCounter sourceCounter; 76 | 77 | private String confDirs; 78 | 79 | private Map dirMap; 80 | private Map pathMap; 81 | 82 | private ExecutorService executorService; 83 | private MonitorRunnable monitorRunnable; 84 | private Future monitorFuture; 85 | 86 | private long keepBufferInterval; 87 | private long fileMonitorSleepTime; 88 | 89 | DirectoryTailParserModulable parserModule; 90 | 91 | private DefaultFileMonitor fileMonitor; 92 | 93 | // private static final long eventQueueWorkerTimeoutMiliSecond = 1000; 94 | private static final int eventQueueWorkerSize = 10; 95 | private static final int maxEventQueueSize = 1000 * 1000; 96 | private BlockingQueue eventQueue = new LinkedBlockingQueue( 97 | maxEventQueueSize); 98 | private Future[] workerFuture = new Future[eventQueueWorkerSize]; 99 | 100 | private FileSystemManager fsManager; 101 | private Hashtable fileSetMap; 102 | 103 | /** 104 | *
105 |    * 1. MethodName : configure
106 |    * 2. ClassName  : DirectoryTailSource
107 |    * 3. Comment   :
108 |    * 4. 작성자    : Jinoos Lee 
109 |    * 5. 작성일    : 2013. 11. 1. 오후 12:51:20
110 |    * 
111 | * 112 | * @param context 113 | */ 114 | public void configure(Context context) { 115 | logger.info("Source Configuring.."); 116 | 117 | dirMap = new HashMap(); 118 | pathMap = new HashMap(); 119 | 120 | keepBufferInterval = context.getLong(CONFIG_KEEP_BUFFER_INTERVAL, 121 | DEFAULT_KEEP_BUFFER_INTERVAL); 122 | 123 | fileMonitorSleepTime = context.getLong(CONFIG_FILE_MONITOR_SLEEP_TIME, 124 | DEFAULT_FILE_MONITOR_SLEEP_TIME); 125 | 126 | confDirs = context.getString(CONFIG_DIRS).trim(); 127 | Preconditions.checkState(confDirs != null, 128 | "Configuration must be specified directory(ies)."); 129 | 130 | String[] confDirArr = confDirs.split(" "); 131 | 132 | Preconditions.checkState(confDirArr.length > 0, CONFIG_DIRS 133 | + " must be specified at least one."); 134 | 135 | for (int i = 0; i < confDirArr.length; i++) { 136 | 137 | String path = context.getString(CONFIG_DIRS + CONFIG_SEPERATOR 138 | + confDirArr[i] + CONFIG_SEPERATOR + CONFIG_PATH); 139 | if (path == null) { 140 | logger 141 | .warn("Configuration is empty : " + CONFIG_DIRS + CONFIG_SEPERATOR 142 | + confDirArr[i] + CONFIG_SEPERATOR + CONFIG_PATH); 143 | continue; 144 | } 145 | 146 | String patternString = context.getString(CONFIG_DIRS + CONFIG_SEPERATOR 147 | + confDirArr[i] + CONFIG_SEPERATOR + CONFIG_FILE_PATTERN); 148 | if (patternString == null) { 149 | patternString = DEFAULT_FILE_PATTERN; 150 | } 151 | 152 | Pattern pattern = null; 153 | try { 154 | pattern = Pattern.compile(patternString); 155 | } catch (PatternSyntaxException e) { 156 | logger.warn("Configuration has wrong file pattern, " + CONFIG_DIRS 157 | + "." + confDirArr[i] + "." + CONFIG_FILE_PATTERN + ":" 158 | + patternString); 159 | logger.warn("Directory will be set default file pattern, " 160 | + DEFAULT_FILE_PATTERN); 161 | 162 | pattern = Pattern.compile(patternString); 163 | } 164 | 165 | DirPattern dir = new DirPattern(); 166 | dir.setPath(path); 167 | dir.setFilePattern(pattern); 168 | 169 | dirMap.put(confDirArr[i], dir); 170 | logger.debug("parsed dirs configure dir : " + confDirArr[i] + ", path : " 171 | + path); 172 | } 173 | 174 | String confParserModule = DEFAULT_PARSER_MODULE_CLASS; 175 | try { 176 | confParserModule = context.getString(CONFIG_PARSER, 177 | DEFAULT_PARSER_MODULE_CLASS); 178 | parserModule = (DirectoryTailParserModulable) Class.forName( 179 | confParserModule).newInstance(); 180 | logger.debug("parserClass : " + confParserModule); 181 | } catch (ClassNotFoundException e) { 182 | Preconditions.checkState(false, e.getMessage() + " " + confParserModule); 183 | logger.error(e.getMessage(), e); 184 | } catch (InstantiationException e) { 185 | Preconditions.checkState(false, e.getMessage() + " " + confParserModule); 186 | logger.error(e.getMessage(), e); 187 | } catch (IllegalAccessException e) { 188 | Preconditions.checkState(false, e.getMessage() + " " + confParserModule); 189 | logger.error(e.getMessage(), e); 190 | } 191 | 192 | parserModule.configure(context); 193 | } 194 | 195 | /** 196 | *
197 |    * 1. MethodName : start
198 |    * 2. ClassName  : WasAppLogSource
199 |    * 3. Comment   :
200 |    * 4. 작성자    : jinoos
201 |    * 5. 작성일    : 2013. 10. 16. 오후 10:43:06
202 |    * 
203 | */ 204 | @Override 205 | public void start() { 206 | logger.info("Source Starting.."); 207 | 208 | if (sourceCounter == null) { 209 | sourceCounter = new SourceCounter(getName()); 210 | } 211 | 212 | fileSetMap = new Hashtable(); 213 | 214 | try { 215 | fsManager = VFS.getManager(); 216 | } catch (FileSystemException e) { 217 | logger.error(e.getMessage(), e); 218 | return; 219 | } 220 | 221 | monitorRunnable = new MonitorRunnable(); 222 | 223 | fileMonitor = new DefaultFileMonitor(monitorRunnable); 224 | fileMonitor.setRecursive(false); 225 | 226 | FileObject fileObject; 227 | 228 | logger.debug("Dirlist count " + dirMap.size()); 229 | for (Entry entry : dirMap.entrySet()) { 230 | logger.debug("Scan dir " + entry.getKey()); 231 | 232 | DirPattern dirPattern = entry.getValue(); 233 | 234 | try { 235 | fileObject = fsManager.resolveFile(dirPattern.getPath()); 236 | } catch (FileSystemException e) { 237 | logger.error(e.getMessage(), e); 238 | continue; 239 | } 240 | 241 | try { 242 | if (!fileObject.isReadable()) { 243 | logger.warn("No have readable permission, " + fileObject.getURL()); 244 | continue; 245 | } 246 | 247 | if (FileType.FOLDER != fileObject.getType()) { 248 | logger.warn("Not a directory, " + fileObject.getURL()); 249 | continue; 250 | } 251 | 252 | // 폴더를 Monitoring 대상에 추가한다. 253 | fileMonitor.addFile(fileObject); 254 | logger.debug(fileObject.getName().getPath() 255 | + " directory has been add in monitoring list"); 256 | pathMap.put(fileObject.getName().getPath(), entry.getValue()); 257 | } catch (FileSystemException e) { 258 | logger.warn(e.getMessage(), e); 259 | continue; 260 | } catch (Exception e) { 261 | logger.debug(e.getMessage(), e); 262 | } 263 | 264 | } 265 | 266 | executorService = Executors.newFixedThreadPool(eventQueueWorkerSize + 1); 267 | monitorFuture = executorService.submit(monitorRunnable); 268 | 269 | for (int i = 0; i < eventQueueWorkerSize; i++) { 270 | workerFuture[i] = executorService.submit(new WorkerRunnable(this)); 271 | } 272 | 273 | sourceCounter.start(); 274 | super.start(); 275 | } 276 | 277 | /** 278 | *
279 |    * 1. MethodName : stop
280 |    * 2. ClassName  : WasAppLogSource
281 |    * 3. Comment   :
282 |    * 4. 작성자    : jinoos
283 |    * 5. 작성일    : 2013. 10. 16. 오후 10:43:03
284 |    * 
285 | */ 286 | @Override 287 | public void stop() { 288 | logger.info("Source Stopping.."); 289 | fileMonitor.stop(); 290 | sourceCounter.stop(); 291 | 292 | } 293 | 294 | private class WorkerRunnable implements Runnable { 295 | private AbstractSource source; 296 | 297 | private WorkerRunnable(AbstractSource source) { 298 | this.source = source; 299 | } 300 | 301 | public void run() { 302 | while (true) { 303 | try { 304 | // DirectoryTailEvent event = eventQueue.poll( 305 | // eventQueueWorkerTimeoutMiliSecond, 306 | // TimeUnit.MILLISECONDS); 307 | DirectoryTailEvent event = eventQueue.take(); 308 | 309 | if (event == null) { 310 | continue; 311 | } 312 | 313 | if (event.type == FileEventType.FILE_CHANGED) { 314 | fileChanged(event.event); 315 | } else if (event.type == FileEventType.FILE_CREATED) { 316 | fileCreated(event.event); 317 | } else if (event.type == FileEventType.FILE_DELETED) { 318 | fileDeleted(event.event); 319 | } else if (event.type == FileEventType.FLUSH) { 320 | if (event.fileSet != null) 321 | sendEvent(event.fileSet); 322 | } 323 | } catch (InterruptedException e) { 324 | logger.debug(e.getMessage(), e); 325 | } catch (FileSystemException e) { 326 | logger.info(e.getMessage(), e); 327 | } 328 | } 329 | } 330 | 331 | private void fileCreated(FileChangeEvent event) throws FileSystemException { 332 | String path = event.getFile().getName().getPath(); 333 | String dirPath = event.getFile().getParent().getName().getPath(); 334 | 335 | logger.debug(path + " has been created."); 336 | 337 | DirPattern dirPattern = null; 338 | dirPattern = pathMap.get(dirPath); 339 | 340 | if (dirPattern == null) { 341 | logger.warn("Occurred create event from un-indexed directory. " 342 | + dirPath); 343 | return; 344 | } 345 | 346 | // 파일명이 대상인지 검사한다. 347 | if (!isInFilePattern(event.getFile(), dirPattern.getFilePattern())) { 348 | logger.debug(path + " is not in file pattern."); 349 | return; 350 | } 351 | 352 | FileSet fileSet; 353 | 354 | fileSet = fileSetMap.get(event.getFile().getName().getPath()); 355 | 356 | if (fileSet == null) { 357 | try { 358 | logger.info(path 359 | + " is not in monitoring list. It's going to be listed."); 360 | fileSet = new FileSet(source, event.getFile()); 361 | //a little synchronized bug here.fixed by tqli,2014-08-07 ,E-mail:tiangang1126@126.com 362 | synchronized(fileSetMap){ 363 | fileSetMap.put(path, fileSet); 364 | } 365 | } catch (IOException e) { 366 | logger.error(e.getMessage(), e); 367 | return; 368 | } 369 | } 370 | } 371 | 372 | private void fileDeleted(FileChangeEvent event) throws FileSystemException { 373 | String path = event.getFile().getName().getPath(); 374 | String dirPath = event.getFile().getParent().getName().getPath(); 375 | 376 | logger.debug(path + " has been deleted."); 377 | 378 | DirPattern dirPattern = pathMap.get(dirPath); 379 | if (dirPattern == null) { 380 | logger.warn("Occurred delete event from un-indexed directory. " 381 | + dirPath); 382 | return; 383 | } 384 | 385 | if (!isInFilePattern(event.getFile(), dirPattern.getFilePattern())) { 386 | logger.debug(path + " is not in file pattern."); 387 | return; 388 | } 389 | 390 | FileSet fileSet = fileSetMap.get(path); 391 | 392 | if (fileSet != null) { 393 | //a little synchronized bug here.fixed by tqli,2014-08-07,E-mail:tiangang1126@126.com 394 | synchronized(fileSetMap){ 395 | fileSetMap.remove(path); 396 | } 397 | logger.debug("Removed monitoring fileSet."); 398 | } 399 | } 400 | 401 | private void fileChanged(FileChangeEvent event) throws FileSystemException { 402 | String path = event.getFile().getName().getPath(); 403 | String dirPath = event.getFile().getParent().getName().getPath(); 404 | 405 | logger.debug(path + " has been changed."); 406 | 407 | DirPattern dirPattern = pathMap.get(dirPath); 408 | if (dirPattern == null) { 409 | logger.warn("Occurred change event from un-indexed directory. " 410 | + dirPath); 411 | return; 412 | } 413 | 414 | // 파일명이 대상인지 검사한다. 415 | if (!isInFilePattern(event.getFile(), dirPattern.getFilePattern())) { 416 | logger.debug("Not in file pattern, " + path); 417 | return; 418 | } 419 | 420 | FileSet fileSet = fileSetMap.get(event.getFile().getName().getPath()); 421 | 422 | if (fileSet == null) { 423 | logger.warn(path + "is not in monitoring list."); 424 | try { 425 | fileSet = new FileSet(source, event.getFile()); 426 | synchronized (fileSetMap) { 427 | fileSetMap.put(path, fileSet); 428 | } 429 | } catch (IOException e) { 430 | logger.error(e.getMessage(), e); 431 | return; 432 | } 433 | return; 434 | } 435 | 436 | readMessage(fileSet); 437 | } 438 | 439 | // 파일이 대상 패턴에 존재하는지 검사한다. 440 | private boolean isInFilePattern(FileObject file, Pattern pattern) { 441 | String fileName = file.getName().getBaseName(); 442 | Matcher matcher = pattern.matcher(fileName); 443 | if (matcher.find()) { 444 | return true; 445 | } 446 | return false; 447 | } 448 | 449 | // 파일을 읽고 Event를 생성한다. 450 | private void readMessage(FileSet fileSet) { 451 | try { 452 | String buffer; 453 | 454 | synchronized (fileSet) { 455 | 456 | while ((buffer = fileSet.readLine()) != null) { 457 | if (buffer.length() == 0) { 458 | continue; 459 | } 460 | 461 | boolean isFirstLine = parserModule.isFirstLine(buffer); 462 | if (isFirstLine) { 463 | sendEvent(fileSet); 464 | fileSet.appendLine(buffer); 465 | parserModule.parse(buffer, fileSet); 466 | 467 | } else { 468 | if (fileSet.getLineSize() == 0) { 469 | logger.debug("Wrong log format, " + buffer); 470 | continue; 471 | } else { 472 | fileSet.appendLine(buffer); 473 | parserModule.parse(buffer, fileSet); 474 | } 475 | } 476 | 477 | if (parserModule.isLastLine(buffer)) { 478 | sendEvent(fileSet); 479 | } 480 | } 481 | } 482 | } catch (IOException e) { 483 | logger.warn(e.getMessage(), e); 484 | } 485 | } 486 | 487 | private void sendEvent(FileSet fileSet) { 488 | if (fileSet.getBufferList().isEmpty()) 489 | return; 490 | 491 | synchronized (fileSet) { 492 | StringBuffer sb = fileSet.getAllLines(); 493 | Event event = EventBuilder.withBody(String.valueOf(sb).getBytes(), 494 | fileSet.getHeaders()); 495 | source.getChannelProcessor().processEvent(event); 496 | sourceCounter.incrementEventReceivedCount(); 497 | 498 | fileSet.clear(); 499 | } 500 | } 501 | 502 | } 503 | 504 | private class MonitorRunnable implements Runnable, FileListener { 505 | public void run() { 506 | 507 | fileMonitor.setDelay(fileMonitorSleepTime); 508 | fileMonitor.start(); 509 | 510 | while (true) { 511 | try { 512 | Thread.sleep(keepBufferInterval); 513 | fileMonitor.run(); 514 | } catch (InterruptedException e) { 515 | logger.debug(e.getMessage(), e); 516 | } 517 | 518 | flushFileSetBuffer(); 519 | } 520 | } 521 | 522 | // 파일생성을 감지함. 523 | public void fileCreated(FileChangeEvent event) throws Exception { 524 | DirectoryTailEvent dtEvent = new DirectoryTailEvent(event, 525 | FileEventType.FILE_CREATED); 526 | eventQueue.put(dtEvent); 527 | } 528 | 529 | // 파일 삭제를 감지함. 530 | public void fileDeleted(FileChangeEvent event) throws Exception { 531 | DirectoryTailEvent dtEvent = new DirectoryTailEvent(event, 532 | FileEventType.FILE_DELETED); 533 | eventQueue.put(dtEvent); 534 | } 535 | 536 | // 파일 변경을 감지함. 537 | public void fileChanged(FileChangeEvent event) throws Exception { 538 | DirectoryTailEvent dtEvent = new DirectoryTailEvent(event, 539 | FileEventType.FILE_CHANGED); 540 | eventQueue.put(dtEvent); 541 | } 542 | 543 | public void flush(FileSet fileSet) { 544 | DirectoryTailEvent dtEvent = new DirectoryTailEvent(fileSet); 545 | try { 546 | eventQueue.put(dtEvent); 547 | } catch (InterruptedException e) { 548 | logger.warn(e.getMessage(), e); 549 | } 550 | } 551 | 552 | private void flushFileSetBuffer() { 553 | synchronized (fileSetMap) { 554 | long cutTime = System.currentTimeMillis() - keepBufferInterval; 555 | 556 | for (Map.Entry entry : fileSetMap.entrySet()) { 557 | 558 | // If lastAppendTime is over than keepBufferInterval, 559 | // then, the message will be flushed even not be catched 560 | // new first line or last line. It's to prevent last message 561 | // delay delevery. 562 | if (entry.getValue().getBufferList().size() > 0 563 | && entry.getValue().getLastAppendTime() < cutTime) { 564 | flush(entry.getValue()); 565 | } 566 | } 567 | } 568 | } 569 | } 570 | 571 | private enum FileEventType { 572 | FILE_CREATED, FILE_CHANGED, FILE_DELETED, FLUSH 573 | } 574 | 575 | private class DirectoryTailEvent { 576 | FileChangeEvent event; 577 | FileEventType type; 578 | FileSet fileSet; 579 | 580 | public DirectoryTailEvent(FileChangeEvent event, FileEventType type) { 581 | this.type = type; 582 | this.event = event; 583 | this.fileSet = null; 584 | } 585 | 586 | public DirectoryTailEvent(FileSet fileSet) { 587 | this.type = FileEventType.FLUSH; 588 | this.fileSet = fileSet; 589 | this.event = null; 590 | } 591 | } 592 | } 593 | -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/FileSet.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import org.apache.commons.vfs2.FileObject; 13 | import org.apache.flume.Transaction; 14 | import org.apache.flume.source.AbstractSource; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | public class FileSet { 19 | private static final Logger logger = LoggerFactory.getLogger(FileSet.class); 20 | private AbstractSource source; 21 | private FileObject fileObject; 22 | private BufferedReader bufferedReader; 23 | private RandomAccessFile rReader; 24 | private Transaction transaction; 25 | private List bufferList; 26 | private Map headers; 27 | private long lastAppendTime; 28 | private Long seq; 29 | 30 | public FileSet(AbstractSource source, FileObject fileObject) 31 | throws IOException { 32 | this.source = source; 33 | this.fileObject = fileObject; 34 | 35 | this.bufferList = new ArrayList(); 36 | 37 | File f = new File(fileObject.getName().getPath()); 38 | 39 | rReader = new RandomAccessFile(f, "r"); 40 | rReader.seek(f.length()); 41 | 42 | bufferList = new ArrayList(); 43 | headers = new HashMap(); 44 | logger.debug("FileSet has been created " + fileObject.getName().getPath()); 45 | this.seq = 0L; 46 | } 47 | 48 | public String readLine() throws IOException { 49 | return rReader.readLine(); 50 | } 51 | 52 | public long getLastAppendTime() { 53 | return lastAppendTime; 54 | } 55 | 56 | public void setLastAppendTime(long lastAppendTime) { 57 | this.lastAppendTime = lastAppendTime; 58 | } 59 | 60 | public boolean appendLine(String buffer) { 61 | boolean ret = bufferList.add(buffer); 62 | if (ret) { 63 | lastAppendTime = System.currentTimeMillis(); 64 | } 65 | 66 | return ret; 67 | } 68 | 69 | public int getLineSize() { 70 | return bufferList.size(); 71 | } 72 | 73 | public StringBuffer getAllLines() { 74 | 75 | StringBuffer sb = new StringBuffer(); 76 | 77 | for (int i = 0; i < bufferList.size(); i++) { 78 | sb.append(bufferList.get(i)); 79 | } 80 | return sb; 81 | } 82 | 83 | public void setHeader(String key, String value) { 84 | headers.put(key, value); 85 | } 86 | 87 | public String getHeader(String key) { 88 | headers.get(key); 89 | return null; 90 | } 91 | 92 | public void clear() { 93 | bufferList.clear(); 94 | headers.clear(); 95 | } 96 | 97 | public Map getHeaders() { 98 | return headers; 99 | } 100 | 101 | public AbstractSource getSource() { 102 | return source; 103 | } 104 | 105 | public void setSource(AbstractSource source) { 106 | this.source = source; 107 | } 108 | 109 | public List getBufferList() { 110 | return bufferList; 111 | } 112 | 113 | public void setBufferList(List bufferList) { 114 | this.bufferList = bufferList; 115 | } 116 | 117 | public Transaction getTransaction() { 118 | return transaction; 119 | } 120 | 121 | public void setTransaction(Transaction transaction) { 122 | this.transaction = transaction; 123 | } 124 | 125 | public FileObject getFileObject() { 126 | return fileObject; 127 | } 128 | 129 | public void setFileObject(FileObject fileObject) { 130 | this.fileObject = fileObject; 131 | } 132 | 133 | public BufferedReader getBufferedReader() { 134 | return bufferedReader; 135 | } 136 | 137 | public void setBufferedReader(BufferedReader bufferedReader) { 138 | this.bufferedReader = bufferedReader; 139 | } 140 | 141 | public Long getSeq() { 142 | return ++seq; 143 | } 144 | 145 | /* 146 | * public void setSeq(Long seq) { this.seq = seq; } 147 | */ 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/MultiLineParserModule.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | import java.util.regex.PatternSyntaxException; 6 | 7 | import org.apache.flume.Context; 8 | import org.apache.flume.conf.Configurable; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import com.google.common.base.Preconditions; 13 | 14 | public class MultiLineParserModule implements Configurable, 15 | DirectoryTailParserModulable { 16 | private static final Logger logger = LoggerFactory 17 | .getLogger(MultiLineParserModule.class); 18 | 19 | private static final String CONFIG_FIRST_LINE_PATTERN = "first-line-pattern"; 20 | private Pattern pattern; 21 | private String patternString; 22 | 23 | public void flush() { 24 | } 25 | 26 | public void parse(String line, FileSet header) { 27 | } 28 | 29 | public boolean isFirstLine(String line) { 30 | Matcher matcher = pattern.matcher(line); 31 | 32 | return matcher.find(); 33 | } 34 | 35 | public boolean isLastLine(String line) { 36 | return false; 37 | } 38 | 39 | public void configure(Context context) { 40 | patternString = context.getString(CONFIG_FIRST_LINE_PATTERN); 41 | Preconditions.checkState(patternString != null, 42 | "Configuration must specify first-line-pattern."); 43 | 44 | try { 45 | pattern = Pattern.compile(patternString); 46 | } catch (PatternSyntaxException e) { 47 | Preconditions.checkState(pattern != null, e.getMessage()); 48 | logger.error(e.getMessage(), e); 49 | return; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/SingleLineParserModule.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import org.apache.flume.Context; 7 | import org.apache.flume.conf.Configurable; 8 | 9 | public class SingleLineParserModule implements Configurable, 10 | DirectoryTailParserModulable { 11 | 12 | public void flush() { 13 | } 14 | 15 | public void parse(String line, FileSet header) { 16 | } 17 | 18 | public boolean isFirstLine(String line) { 19 | return false; 20 | } 21 | 22 | public boolean isLastLine(String line) { 23 | return true; 24 | } 25 | 26 | public void configure(Context context) { 27 | // TODO Auto-generated method stub 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/jinoos/flume/ThreadSafeSimpleDateFormat.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | import java.text.ParseException; 7 | 8 | public class ThreadSafeSimpleDateFormat { 9 | private DateFormat df; 10 | 11 | public ThreadSafeSimpleDateFormat(String format) 12 | { 13 | this.df = new SimpleDateFormat(format); 14 | } 15 | 16 | public synchronized String format(Date date) 17 | { 18 | return df.format(date); 19 | } 20 | 21 | public synchronized Date parse(String string) throws ParseException { 22 | return df.parse(string); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/jinoos/flume_ng_extends/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.jinoos.flume_ng_extends; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | --------------------------------------------------------------------------------