Skip to content

Commit 0c7fdcb

Browse files
Merge pull request #17 from moka-guys/development
Fix logging (#17) Co-Authored-By: Graeme <graeme_c_smith@hotmail.com> Co-Authored-By: rebeccahaines1 <84131466+rebeccahaines1@users.noreply.github.com>
2 parents 02b1f5b + 872b630 commit 0c7fdcb

File tree

6 files changed

+68
-62
lines changed

6 files changed

+68
-62
lines changed

samplesheet_validator/__main__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import os
2+
import sys
3+
import logging
24
import argparse
35
from .samplesheet_validator import SamplesheetCheck
46
from .ss_logger import set_root_logger
7+
from .config import LOGGING_FORMATTER
58

69

710
def get_arguments():
@@ -98,10 +101,10 @@ def is_valid_dir(parser: argparse.ArgumentParser, dir: str) -> str:
98101
return dir
99102

100103

104+
101105
if __name__ == "__main__":
102106
parsed_args = get_arguments()
103-
if not parsed_args.no_stream_handler:
104-
set_root_logger() # Adds stream handler
107+
logger = set_root_logger(parsed_args.no_stream_handler)
105108
sscheck_obj = SamplesheetCheck(
106109
parsed_args.samplesheet_path,
107110
parsed_args.sequencer_ids,
@@ -111,3 +114,4 @@ def is_valid_dir(parser: argparse.ArgumentParser, dir: str) -> str:
111114
parsed_args.logdir,
112115
)
113116
sscheck_obj.ss_checks() # Carry out samplesheeet validation
117+

samplesheet_validator/config.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
TIMESTAMP = str(f"{datetime.datetime.now():%Y%m%d_%H%M%S}")
44

55
# Specifies the layout of log records in the final output
6-
LOGGING_FORMATTER = "%(asctime)s - SAMPLESHEET_VALIDATOR - %(levelname)s - %(message)s"
6+
LOGGING_FORMATTER = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
77

88
LOG_MSGS = {
99
"ss_present": "Samplesheet with supplied name exists (%s)",
@@ -14,11 +14,11 @@
1414
"sequencer_id_invalid": "Sequencer id not in allowed list (%s, %s)",
1515
"ss_not_empty": "Samplesheet is (>10 bytes)",
1616
"ss_empty": "Samplesheet empty (<10 bytes)",
17-
"found_header_line": "Line in samplesheet identified as a header line",
18-
"found_sample_line": "Line in samplesheet identified as containing a sample",
19-
"error_extracting_headers": "An error was encountered when extracting headers from the samplesheet: %s",
20-
"found_empty_line": "Line in samplesheet is an empty line",
21-
"col_extraction_error": "Exception raised while attempting to extract %s from sample line %s: %s",
17+
"found_header_line": "Line %s in samplesheet identified as a header line",
18+
"found_sample_line": "Line %s in samplesheet identified as containing a sample",
19+
"error_extracting_headers": "An error was encountered when extracting headers from the samplesheet, from line %s: %s",
20+
"found_empty_line": "Line %s in samplesheet is an empty line",
21+
"col_extraction_error": "Exception raised while attempting to extract %s from sample line %s, %s: %s",
2222
"headers_as_expected": "Expected headers present in samplesheet",
2323
"headers_err": "Header(/s) missing from [Data] section: '%s'",
2424
"samplenames_match": "All sample names and sample IDS match",

samplesheet_validator/samplesheet_validator.py

+29-25
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"""
1313
import os
1414
import re
15+
import logging
1516
from typing import Union
1617
from . import config
1718
from .ss_logger import SSLogger
@@ -42,6 +43,9 @@ class SamplesheetCheck:
4243
panels (list): Valid pan numbers
4344
tso_panels (list): Valid TSO pannumbers
4445
development_panels (list): Development pan numbers
46+
runfolder_name (str): Name of runfolder
47+
logfile_path (str): Path to use for logfile
48+
logger (logging.Logger): Logger
4549
4650
Methods:
4751
get_logger()
@@ -115,24 +119,24 @@ def __init__(
115119
self.panels = panels
116120
self.tso_panels = tso_panels
117121
self.dev_panno = dev_panno
122+
self.runfolder_name = (self.samplesheet_path.split("/")[-1]).split("_SampleSheet.csv")[0]
123+
self.logfile_path = f"{os.path.join(logdir, self.runfolder_name)}_samplesheet_validator.log"
124+
self.logger = self.get_logger()
125+
118126

119-
def get_logger(self):
127+
def get_logger(self) -> logging.Logger:
120128
"""
121129
Get logger for the class
122-
:return (object): Logger
130+
:return (object): Logger
123131
"""
124-
runfolder_name = (self.samplesheet_path.split("/")[-1]).split(
125-
"_SampleSheet.csv"
126-
)[0]
127-
logfile_path = f"{os.path.join(self.logdir, runfolder_name)}_{config.TIMESTAMP}_samplesheet_validator.log"
128-
return SSLogger(logfile_path).get_logger()
132+
return SSLogger(self.logfile_path, self.runfolder_name).get_logger(__name__)
133+
129134

130135
def ss_checks(self) -> None:
131136
"""
132137
Run checks at samplesheet and sample level. Performs required extra checks for
133138
checks not included in seglh-naming
134139
"""
135-
self.logger = self.get_logger()
136140
if self.check_ss_present():
137141
setattr(self, "ss_obj", self.check_ss_name())
138142
if self.ss_obj:
@@ -274,57 +278,57 @@ def get_data_section(self) -> None:
274278
:return None:
275279
"""
276280
with open(self.samplesheet_path, "r") as samplesheet_stream:
277-
for line in reversed(samplesheet_stream.readlines()):
281+
samplesheet_contents = samplesheet_stream.readlines()
282+
for line in reversed(samplesheet_contents):
283+
line_index = samplesheet_contents.index(line)
278284
# If line contains table headers, stop looping through the file
279285
if any(header in line for header in self.expected_data_headers):
280-
self.extract_headers(line)
286+
self.extract_headers(line, line_index)
281287
break
282288
elif len(line.split(",")[0]) < 2:
283-
self.logger.info(self.logger.log_msgs["found_empty_line"])
289+
self.logger.info(self.logger.log_msgs["found_empty_line"], line_index)
284290
pass # Skip empty lines
285291
else: # Contains sample
286-
self.extract_sample_name_id(line)
292+
self.extract_sample_name_id(line, line_index)
287293

288-
def extract_headers(self, line: str) -> None:
294+
def extract_headers(self, line: str, line_index: int) -> None:
289295
"""
290296
Extract headers from line
291-
:param line (str): Line containing samplesheet headers
297+
:param line (str): Line containing samplesheet headers
298+
:param line_index (int): Index of line
292299
"""
293300
try:
294-
self.logger.info(self.logger.log_msgs["found_header_line"])
301+
self.logger.info(self.logger.log_msgs["found_header_line"], line_index)
295302
self.data_headers = line.split(",")
296303
except Exception as exception:
297304
self.errors = True
298305
self.logger.warning(
299-
self.logger.log_msgs["error_extracting_headers"], exception
306+
self.logger.log_msgs["error_extracting_headers"], line_index, exception
300307
)
301308
self.add_msg_to_error_dict(
302309
"Error extracting headers",
303-
self.logger.log_msgs["error_extracting_headers"] % exception,
310+
self.logger.log_msgs["error_extracting_headers"] % (line_index, exception),
304311
)
305312

306-
def extract_sample_name_id(self, line: str) -> None:
313+
def extract_sample_name_id(self, line: str, line_index: int) -> None:
307314
"""
308315
Extract sample name and sample id from samplesheet line
309316
:param line (str): Line containing sample details
317+
:param line_index (int): Index of line
310318
"""
311-
self.logger.info(self.logger.log_msgs["found_sample_line"])
319+
self.logger.info(self.logger.log_msgs["found_sample_line"], line_index)
312320
for column_details in [("Sample_ID", 0), ("Sample_Name", 1)]:
313321
col_name, index = column_details
314322
try:
315323
self.samples[col_name].append(line.split(",")[index])
316324
except Exception as exception:
317325
self.errors = True
318326
self.logger.warning(
319-
self.logger.log_msgs["col_extraction_error"],
320-
col_name,
321-
line,
322-
exception,
327+
self.logger.log_msgs["col_extraction_error"], col_name, line_index, line, exception,
323328
)
324329
self.add_msg_to_error_dict(
325330
"Error extracting sample name and ID",
326-
self.logger.log_msgs["col_extraction_error"]
327-
% (col_name, line, exception),
331+
self.logger.log_msgs["col_extraction_error"] % (col_name, line_index, line, exception)
328332
)
329333

330334
def check_expected_headers(self) -> None:

samplesheet_validator/ss_logger.py

+25-27
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,68 @@
88
import logging.handlers
99

1010

11-
def set_root_logger():
11+
def set_root_logger(no_stream_handler: bool):
1212
"""
13-
Set up root logger and add stream handler - we only want to add stream handler once
14-
else it will duplicate log messages to the terminal
13+
Set up root logger and add stream handler and syslog handler - we only want to add these once
14+
else it will duplicate log messages to the terminal. All loggers named with the same stem
15+
as the root logger will use these same syslog handler and stream handler
16+
:param no_stream_handler (bool): True if no stream handler specified as command line input
1517
"""
18+
formatter = logging.Formatter(config.LOGGING_FORMATTER)
1619
logger = logging.getLogger()
1720
logger.setLevel(logging.DEBUG)
18-
stream_handler = logging.StreamHandler(sys.stdout)
19-
stream_handler.setLevel(logging.DEBUG)
20-
stream_handler.setFormatter(logging.Formatter(config.LOGGING_FORMATTER))
21-
stream_handler.name = "stream_handler"
22-
logger.addHandler(stream_handler)
21+
syslog_handler = logging.handlers.SysLogHandler(address="/dev/log")
22+
syslog_handler.setFormatter(formatter)
23+
syslog_handler.name = "syslog_handler"
24+
logger.addHandler(syslog_handler)
25+
if not no_stream_handler:
26+
stream_handler = logging.StreamHandler(sys.stdout)
27+
stream_handler.setFormatter(formatter)
28+
stream_handler.name = "stream_handler"
29+
logger.addHandler(stream_handler)
30+
return logger
31+
2332

2433

2534
class SSLogger:
2635
"""
2736
Creates a python logging object with a file handler and syslog handler
2837
2938
Attributes
30-
timestamp (str): Timestamp from config
3139
logfile_path (str): Name of filepath to provide to _file_handler()
40+
runfolder_name (str): Runfolder name
3241
logging_formatter (logging.Formatter): Specifies the layout of log records in the final output
3342
3443
Methods
35-
get_logger()
44+
get_logger(logger_name)
3645
Returns a Python logging object
3746
_get_file_handler()
3847
Get file handler for the logger
3948
_get_syslog_handler()
4049
Get syslog handler for the logger
4150
"""
4251

43-
def __init__(self, logfile_path: str):
52+
def __init__(self, logfile_path: str, runfolder_name: str):
4453
"""
4554
Constructor for the Logger class
4655
:param logfile_path (str): Path to logfile location
56+
:param runfolder_name (str): Runfolder name
4757
"""
4858
# Timestamp used for naming log files with datetime, format %Y%m%d_%H%M%S
49-
self.timestamp = config.TIMESTAMP
5059
self.logfile_path = logfile_path
60+
self.runfolder_name = runfolder_name
5161
self.logging_formatter = logging.Formatter(config.LOGGING_FORMATTER)
5262

53-
def get_logger(self) -> logging.Logger:
63+
def get_logger(self, logger_name: str) -> logging.Logger:
5464
"""
5565
Returns a Python logging object, and give it a name
66+
:param logger_name (str): Logger name string
5667
:return logger (object): Python logging object with custom attributes
5768
"""
58-
logger = logging.getLogger()
69+
logger = logging.getLogger(f"{logger_name}.{self.runfolder_name}")
5970
logger.filepath = self.logfile_path
6071
logger.setLevel(logging.DEBUG)
6172
logger.addHandler(self._get_file_handler())
62-
logger.addHandler(self._get_syslog_handler())
63-
logger.timestamp = self.timestamp
6473
logger.log_msgs = config.LOG_MSGS
6574
return logger
6675

@@ -74,14 +83,3 @@ def _get_file_handler(self) -> logging.FileHandler:
7483
file_handler.setFormatter(self.logging_formatter)
7584
file_handler.name = "file_handler"
7685
return file_handler
77-
78-
def _get_syslog_handler(self) -> logging.handlers.SysLogHandler:
79-
"""
80-
Get syslog handler for the logger, and give it a name
81-
:return syslog_handler (logging.SysLogHandler): SysLogHandler
82-
"""
83-
syslog_handler = logging.handlers.SysLogHandler(address="/dev/log")
84-
syslog_handler.setLevel(logging.DEBUG)
85-
syslog_handler.setFormatter(self.logging_formatter)
86-
syslog_handler.name = "syslog_handler"
87-
return syslog_handler

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
setup(
55
name="samplesheet_validator",
6-
version=git_tag(),
6+
version="TEST",
77
description="Python library for samplesheet validation",
88
url="https://github.com/moka-guys/samplesheet_validator",
99
author="Rachel Duffin",

test/test_samplesheet_validator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ def test_extract_sample_name_id_pass(self, valid_samplesheets_with_dev, caplog):
904904
for samplesheet in valid_samplesheets_with_dev:
905905
sscheck_obj = get_sscheck_obj(samplesheet)
906906
assert (
907-
"Line in samplesheet identified as containing a sample" in caplog.text
907+
"samplesheet identified as containing a sample" in caplog.text
908908
)
909909
shutdown_logs(sscheck_obj.logger)
910910

0 commit comments

Comments
 (0)