Skip to content

Commit 26f62b6

Browse files
authored
Merge pull request #2845 from alicevision/dev/update_chunk_logger
[core] Setup logger to be able to use logging in plugins
2 parents 077135e + 4e8bb22 commit 26f62b6

File tree

7 files changed

+220
-28
lines changed

7 files changed

+220
-28
lines changed

bin/meshroom_batch

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import re
88
import sys
99

1010
import meshroom.core.graph
11-
from meshroom import setupEnvironment
11+
from meshroom import setupEnvironment, logStringToPython
1212

1313
setupEnvironment()
1414

@@ -135,14 +135,6 @@ advanced_group.add_argument(
135135

136136
args = parser.parse_args()
137137

138-
logStringToPython = {
139-
'fatal': logging.FATAL,
140-
'error': logging.ERROR,
141-
'warning': logging.WARNING,
142-
'info': logging.INFO,
143-
'debug': logging.DEBUG,
144-
'trace': logging.DEBUG,
145-
}
146138
logging.getLogger().setLevel(logStringToPython[args.verbose])
147139

148140
if not args.input and not args.inputRecursive:

bin/meshroom_compute

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,15 @@ if args.node:
9595
# Restore the log level
9696
logging.getLogger().setLevel(meshroom.logStringToPython[args.verbose])
9797

98+
node.prepareLogger(args.iteration)
9899
node.preprocess()
99100
if args.iteration != -1:
100101
chunk = node.chunks[args.iteration]
101102
chunk.process(args.forceCompute, args.inCurrentEnv)
102103
else:
103104
node.process(args.forceCompute, args.inCurrentEnv)
104105
node.postprocess()
106+
node.restoreLogger()
105107
else:
106108
if args.iteration != -1:
107109
print('Error: "--iteration" only make sense when used with "--node".')

meshroom/__init__.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,32 @@ class VersionStatus(Enum):
4848

4949
useMultiChunks = util.strtobool(os.environ.get("MESHROOM_USE_MULTI_CHUNKS", "True"))
5050

51+
# Logging
52+
53+
def addTraceLevel():
54+
""" From https://stackoverflow.com/a/35804945 """
55+
levelName, methodName, levelNum = 'TRACE', 'trace', logging.DEBUG - 5
56+
if hasattr(logging, levelName) or hasattr(logging, methodName)or hasattr(logging.getLoggerClass(), methodName):
57+
return
58+
def logForLevel(self, message, *args, **kwargs):
59+
if self.isEnabledFor(levelNum):
60+
self._log(levelNum, message, args, **kwargs)
61+
def logToRoot(message, *args, **kwargs):
62+
logging.log(levelNum, message, *args, **kwargs)
63+
64+
logging.addLevelName(levelNum, levelName)
65+
setattr(logging, levelName, levelNum)
66+
setattr(logging.getLoggerClass(), methodName, logForLevel)
67+
setattr(logging, methodName, logToRoot)
68+
69+
addTraceLevel()
5170
logStringToPython = {
52-
'fatal': logging.FATAL,
71+
'fatal': logging.CRITICAL,
5372
'error': logging.ERROR,
5473
'warning': logging.WARNING,
5574
'info': logging.INFO,
5675
'debug': logging.DEBUG,
57-
'trace': logging.DEBUG,
76+
'trace': logging.TRACE,
5877
}
5978
logging.getLogger().setLevel(logStringToPython[os.environ.get('MESHROOM_VERBOSE', 'warning')])
6079

meshroom/core/desc/node.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
import sys
1010

1111
from .computation import Level, StaticNodeSize
12-
from .attribute import StringParam, ColorParam
12+
from .attribute import StringParam, ColorParam, ChoiceParam
1313

1414
import meshroom
1515
from meshroom.core import cgroup
16+
from meshroom.core.utils import VERBOSE_LEVEL
1617

1718
_MESHROOM_ROOT = Path(meshroom.__file__).parent.parent.as_posix()
1819
_MESHROOM_COMPUTE = (Path(_MESHROOM_ROOT) / "bin" / "meshroom_compute").as_posix()
@@ -67,6 +68,14 @@ class BaseNode(object):
6768
value="",
6869
invalidate=False,
6970
),
71+
ChoiceParam(
72+
name="nodeDefaultLogLevel",
73+
label="Default Logging Level",
74+
description="Default logging level for the node (critical, error, warning, info, debug).",
75+
value="info",
76+
values=VERBOSE_LEVEL,
77+
invalidate=False,
78+
),
7079
ColorParam(
7180
name="color",
7281
label="Color",

meshroom/core/node.py

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
import uuid
1515
from collections import namedtuple, OrderedDict
1616
from enum import Enum, auto
17-
from typing import Callable, Optional
18-
17+
from typing import Callable, Optional, List
1918

2019
import meshroom
2120
from meshroom.common import Signal, Variant, Property, BaseObject, Slot, ListModel, DictModel
2221
from meshroom.core import desc, plugins, stats, hashValue, nodeVersion, Version, MrNodeType
2322
from meshroom.core.attribute import attributeFactory, ListAttribute, GroupAttribute, Attribute
2423
from meshroom.core.exception import NodeUpgradeError, UnknownNodeTypeError
24+
from meshroom.core.mtyping import PathLike
2525

2626

2727
def getWritingFilepath(filepath: str) -> str:
@@ -221,9 +221,11 @@ def fromDict(self, d):
221221
class LogManager:
222222
dateTimeFormatting = '%H:%M:%S'
223223

224-
def __init__(self, chunk):
225-
self.chunk = chunk
226-
self.logger = logging.getLogger(chunk.node.getName())
224+
def __init__(self, logger, logFile):
225+
self.logger: logging.Logger = logger
226+
self.logFile: PathLike = logFile
227+
self._previousHandlers: List[logging.Handler] = []
228+
self._previousLevel: int = 0
227229

228230
class Formatter(logging.Formatter):
229231
def format(self, record):
@@ -232,19 +234,29 @@ def format(self, record):
232234
return logging.Formatter.format(self, record)
233235

234236
def configureLogger(self):
237+
self._previousLevel = self.logger.level
238+
self._previousHandlers = []
235239
for handler in self.logger.handlers[:]:
240+
self._previousHandlers.append(handler)
236241
self.logger.removeHandler(handler)
237-
handler = logging.FileHandler(self.chunk.logFile)
242+
handler = logging.FileHandler(self.logFile)
238243
formatter = self.Formatter('[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s',
239244
self.dateTimeFormatting)
240245
handler.setFormatter(formatter)
241246
self.logger.addHandler(handler)
242247

248+
def restorePreviousLogger(self):
249+
for h in self.logger.handlers[:]:
250+
self.logger.removeHandler(h)
251+
for h in self._previousHandlers:
252+
self.logger.addHandler(h)
253+
self.logger.setLevel(self._previousLevel)
254+
243255
def start(self, level):
244256
# Clear log file
245-
open(self.chunk.logFile, 'w').close()
246-
257+
open(self.logFile, 'w').close()
247258
self.configureLogger()
259+
self.logger.propagate = False
248260
self.logger.setLevel(self.textToLevel(level))
249261
self.progressBar = False
250262

@@ -261,15 +273,15 @@ def makeProgressBar(self, end, message=''):
261273
self.currentProgressTics = 0
262274
self.progressBar = True
263275

264-
with open(self.chunk.logFile, 'a') as f:
276+
with open(self.logFile, 'a') as f:
265277
if message:
266278
f.write(message+'\n')
267279
f.write('0% 10 20 30 40 50 60 70 80 90 100%\n')
268280
f.write('|----|----|----|----|----|----|----|----|----|----|\n\n')
269281

270282
f.close()
271283

272-
with open(self.chunk.logFile) as f:
284+
with open(self.logFile) as f:
273285
content = f.read()
274286
self.progressBarPosition = content.rfind('\n')
275287

@@ -281,7 +293,7 @@ def updateProgressBar(self, value):
281293

282294
tics = round((value/self.progressEnd)*51)
283295

284-
with open(self.chunk.logFile, 'r+') as f:
296+
with open(self.logFile, 'r+') as f:
285297
text = f.read()
286298
for i in range(tics-self.currentProgressTics):
287299
text = text[:self.progressBarPosition]+'*'+text[self.progressBarPosition:]
@@ -296,8 +308,10 @@ def completeProgressBar(self):
296308

297309
self.progressBar = False
298310

299-
def textToLevel(self, text):
300-
if text == "critical":
311+
@staticmethod
312+
def textToLevel(text):
313+
text = text.lower()
314+
if text in ["critical", "fatal"]:
301315
return logging.CRITICAL
302316
elif text == "error":
303317
return logging.ERROR
@@ -307,6 +321,8 @@ def textToLevel(self, text):
307321
return logging.INFO
308322
elif text == "debug":
309323
return logging.DEBUG
324+
elif text == "trace":
325+
return logging.TRACE
310326
else:
311327
return logging.NOTSET
312328

@@ -325,7 +341,7 @@ def __init__(self, node, range, parent=None):
325341
super().__init__(parent)
326342
self.node = node
327343
self.range = range
328-
self.logManager: LogManager = LogManager(self)
344+
self._logManager = None
329345
self._status: StatusData = StatusData(node.name, node.nodeType, node.packageName,
330346
node.packageVersion, node.getMrNodeType())
331347
self.statistics: stats.Statistics = stats.Statistics()
@@ -344,6 +360,13 @@ def name(self):
344360
return f"{self.node.name}({self.index})"
345361
else:
346362
return self.node.name
363+
364+
@property
365+
def logManager(self):
366+
if self._logManager is None:
367+
logger = logging.getLogger(self.node.getName())
368+
self._logManager = LogManager(logger, self.logFile)
369+
return self._logManager
347370

348371
@property
349372
def statusName(self):
@@ -660,6 +683,7 @@ def __init__(self, nodeType: str, position: Position = None, parent: BaseObject
660683
self._uid: str = uid
661684
self._cmdVars: dict = {}
662685
self._size: int = 0
686+
self._logManager: Optional[LogManager] = None
663687
self._position: Position = position or Position()
664688
self._attributes = DictModel(keyAttrName='name', parent=self)
665689
self._internalAttributes = DictModel(keyAttrName='name', parent=self)
@@ -705,6 +729,15 @@ def getLabel(self):
705729
return label
706730
return self.getDefaultLabel()
707731

732+
def getNodeLogLevel(self):
733+
"""
734+
Returns:
735+
str: the user-provided log level used for logging on process launched by this node
736+
"""
737+
if self.hasInternalAttribute("nodeDefaultLogLevel"):
738+
return self.internalAttribute("nodeDefaultLogLevel").value.strip()
739+
return "info"
740+
708741
def getColor(self):
709742
"""
710743
Returns:
@@ -1312,6 +1345,24 @@ def postprocess(self):
13121345
# node is completed
13131346
self.nodeDesc.postprocess(self)
13141347

1348+
def getLogHandlers(self):
1349+
return self._handlers
1350+
1351+
def prepareLogger(self, iteration=-1):
1352+
# Get file handler path
1353+
logFileName = "log"
1354+
if iteration != -1:
1355+
chunk = self.chunks[iteration]
1356+
logFileName = str(chunk.index) + ".log"
1357+
logFile = os.path.join(self.graph.cacheDir, self.internalFolder, logFileName)
1358+
# Setup logger
1359+
rootLogger = logging.getLogger()
1360+
self._logManager = LogManager(rootLogger, logFile)
1361+
self._logManager.start(self.getNodeLogLevel())
1362+
1363+
def restoreLogger(self):
1364+
self._logManager.restorePreviousLogger()
1365+
13151366
def updateOutputAttr(self):
13161367
if not self.nodeDesc:
13171368
return

meshroom/core/nodeFactory.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,15 @@ def _checkAttributesCompatibility(
174174

175175
def _createNode(self) -> Node:
176176
logging.info(f"Creating node '{self.name}'")
177-
# TODO: user inputs/outputs may conflicts with internal names (like position, uid)
177+
# TODO: user inputs/outputs may conflicts with internal names (like logLevel, position, uid)
178+
# The line below can cause UI issues but at least prevent crashes
179+
internalInputs = {k: v for k, v in self.internalInputs.items() if k not in self.inputs.keys()}
178180
return Node(
179181
self.nodeType,
180182
position=self.position,
181183
uid=self.uid,
182184
**self.inputs,
183-
**self.internalInputs,
185+
**internalInputs,
184186
**self.outputs,
185187
)
186188

0 commit comments

Comments
 (0)