Move all the logging to log.py and prefix all the log names with "erynrl"

This commit is contained in:
Eryn Wells 2022-05-12 20:40:06 -07:00
parent 5d4e0cff3d
commit ce63c825b0
8 changed files with 126 additions and 102 deletions

View file

@ -1,21 +1,16 @@
# Eryn Wells <eryn@erynwells.me>
import argparse
import json
import logging
import logging.config
import os.path
import sys
import tcod
from . import log
from .engine import Configuration, Engine
from .events import EventHandler
from .geometry import Size
LOG = logging.getLogger('main')
CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50
MAP_WIDTH, MAP_HEIGHT = 80, 45
FONT = 'terminal16x16_gs_ro.png'
def parse_args(argv, *a, **kw):
@ -24,23 +19,6 @@ def parse_args(argv, *a, **kw):
args = parser.parse_args(argv)
return args
def init_logging(args):
'''Set up the logging system by (preferrably) reading a logging configuration file.'''
logging_config_path = find_logging_config()
if logging_config_path:
with open(logging_config_path, encoding='utf-8') as logging_config_file:
logging_config = json.load(logging_config_file)
logging.config.dictConfig(logging_config)
LOG.info('Found logging configuration at %s', logging_config_path)
else:
root_logger = logging.getLogger('')
root_logger.setLevel(logging.DEBUG if args.debug else logging.INFO)
stderr_handler = logging.StreamHandler()
stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s"))
root_logger.addHandler(stderr_handler)
def walk_up_directories_of_path(path):
while path and path != '/':
path = os.path.dirname(path)
@ -51,38 +29,26 @@ def find_fonts_directory():
for parent_dir in walk_up_directories_of_path(__file__):
possible_fonts_dir = os.path.join(parent_dir, 'fonts')
if os.path.isdir(possible_fonts_dir):
LOG.info('Found fonts dir %s', possible_fonts_dir)
log.ROOT.info('Found fonts dir %s', possible_fonts_dir)
break
else:
return None
return possible_fonts_dir
def find_logging_config():
'''Walk up the filesystem from this script to find a logging_config.json'''
for parent_dir in walk_up_directories_of_path(__file__):
possible_logging_config_file = os.path.join(parent_dir, 'logging_config.json')
if os.path.isfile(possible_logging_config_file):
LOG.info('Found logging config file %s', possible_logging_config_file)
break
else:
return None
return possible_logging_config_file
def main(argv):
args = parse_args(argv[1:], prog=argv[0])
init_logging(args)
log.init()
fonts_directory = find_fonts_directory()
if not fonts_directory:
LOG.error("Couldn't find a fonts/ directory")
log.ROOT.error("Couldn't find a fonts/ directory")
return -1
font = os.path.join(fonts_directory, FONT)
if not os.path.isfile(font):
LOG.error("Font file %s doesn't exist", font)
log.ROOT.error("Font file %s doesn't exist", font)
return -1
tileset = tcod.tileset.load_tilesheet(font, 16, 16, tcod.tileset.CHARMAP_CP437)

View file

@ -18,18 +18,16 @@ Action : Base class of all actions
WaitAction
'''
import logging
from typing import Optional, TYPE_CHECKING
from . import items
from . import log
from .geometry import Direction
from .object import Actor, Item
if TYPE_CHECKING:
from .engine import Engine
LOG = logging.getLogger('actions')
class ActionResult:
'''The result of an Action.
@ -158,12 +156,12 @@ class BumpAction(MoveAction):
else:
entity_occupying_position = None
LOG.debug('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)',
self.actor,
new_position,
position_is_in_bounds,
position_is_walkable,
entity_occupying_position)
log.ACTIONS.debug('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)',
self.actor,
new_position,
position_is_in_bounds,
position_is_walkable,
entity_occupying_position)
if not position_is_in_bounds or not position_is_walkable:
return self.failure()
@ -180,7 +178,7 @@ class WalkAction(MoveAction):
def perform(self, engine: 'Engine') -> ActionResult:
new_position = self.actor.position + self.direction
LOG.debug('Moving %s to %s', self.actor, new_position)
log.ACTIONS.debug('Moving %s to %s', self.actor, new_position)
self.actor.position = new_position
return self.success()
@ -201,13 +199,13 @@ class MeleeAction(MoveAction):
damage = self.actor.fighter.attack_power - self.target.fighter.defense
if damage > 0 and self.target:
LOG.debug('%s attacks %s for %d damage!', self.actor, self.target, damage)
log.ACTIONS.debug('%s attacks %s for %d damage!', self.actor, self.target, damage)
self.target.fighter.hit_points -= damage
else:
LOG.debug('%s attacks %s but does no damage!', self.actor, self.target)
log.ACTIONS.debug('%s attacks %s but does no damage!', self.actor, self.target)
if self.target.fighter.is_dead:
LOG.info('%s is dead!', self.target)
log.ACTIONS.info('%s is dead!', self.target)
return ActionResult(self, alternate=DieAction(self.target))
return self.success()
@ -216,18 +214,18 @@ class WaitAction(Action):
'''Wait a turn'''
def perform(self, engine: 'Engine') -> ActionResult:
LOG.debug('%s is waiting a turn', self.actor)
log.ACTIONS.debug('%s is waiting a turn', self.actor)
return self.success()
class DieAction(Action):
'''Kill an Actor'''
def perform(self, engine: 'Engine') -> ActionResult:
LOG.debug('%s dies', self.actor)
log.ACTIONS.debug('%s dies', self.actor)
engine.entities.remove(self.actor)
if self.actor.yields_corpse_on_death:
LOG.debug('%s leaves a corpse behind', self.actor)
log.ACTIONS.debug('%s leaves a corpse behind', self.actor)
corpse = Item(kind=items.Corpse, name=f'{self.actor.name} Corpse', position=self.actor.position)
return ActionResult(self, alternate=DropItemAction(self.actor, corpse))

View file

@ -1,12 +1,12 @@
# Eryn Wells <eryn@erynwells.me>
import logging
import random
from typing import TYPE_CHECKING, List, Optional
import numpy as np
import tcod
from . import log
from .actions import Action, BumpAction, WaitAction
from .components import Component
from .geometry import Direction, Point
@ -15,8 +15,6 @@ from .object import Entity
if TYPE_CHECKING:
from .engine import Engine
LOG = logging.getLogger('ai')
class AI(Component):
def __init__(self, entity: Entity) -> None:
super().__init__()
@ -34,7 +32,7 @@ class HostileEnemy(AI):
radius=self.entity.sight_radius)
if engine.map.visible[tuple(self.entity.position)]:
LOG.debug("AI for %s", self.entity)
log.AI.debug("AI for %s", self.entity)
hero_position = engine.hero.position
hero_is_visible = visible_tiles[hero_position.x, hero_position.y]
@ -46,13 +44,13 @@ class HostileEnemy(AI):
entity_position = self.entity.position
if engine.map.visible[tuple(self.entity.position)]:
LOG.debug('|-> Path to hero %s', path_to_hero)
log.AI.debug('|-> Path to hero %s', path_to_hero)
next_position = path_to_hero.pop(0) if len(path_to_hero) > 1 else hero_position
direction_to_next_position = entity_position.direction_to_adjacent_point(next_position)
if engine.map.visible[tuple(self.entity.position)]:
LOG.info('`-> Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position)
log.AI.info('`-> Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position)
return BumpAction(self.entity, direction_to_next_position)
else:
@ -68,13 +66,13 @@ class HostileEnemy(AI):
tile_is_walkable = engine.map.tile_is_walkable(new_position)
if not overlaps_existing_entity and tile_is_walkable:
if engine.map.visible[tuple(self.entity.position)]:
LOG.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction)
log.AI.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction)
action = BumpAction(self.entity, direction)
break
else:
# If this entity somehow can't move anywhere, just wait
if engine.map.visible[tuple(self.entity.position)]:
LOG.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity)
log.AI.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity)
action = WaitAction(self.entity)
return action

View file

@ -2,21 +2,19 @@
'''Defines the core game engine.'''
import logging
import random
from dataclasses import dataclass
from typing import MutableSet
import tcod
from . import log
from . import monsters
from .ai import HostileEnemy
from .geometry import Size
from .map import Map
from .object import Entity, Hero, Monster
LOG = logging.getLogger('engine')
@dataclass
class Configuration:
map_size: Size
@ -66,7 +64,7 @@ class Engine:
else:
monster = Monster(monsters.Orc, ai_class=HostileEnemy, position=random_start_position)
LOG.info('Spawning %s', monster)
log.ENGINE.info('Spawning %s', monster)
self.entities.add(monster)
self.update_field_of_view()

View file

@ -2,11 +2,11 @@
'''Defines event handling mechanisms.'''
import logging
from typing import Optional, TYPE_CHECKING
import tcod
from . import log
from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction
from .geometry import Direction
from .object import Actor
@ -14,9 +14,6 @@ from .object import Actor
if TYPE_CHECKING:
from .engine import Engine
LOG = logging.getLogger('events')
ACTIONS_TREE_LOG = logging.getLogger('actions.tree')
class EventHandler(tcod.event.EventDispatch[Action]):
'''Handler of `tcod` events'''
@ -35,11 +32,11 @@ class EventHandler(tcod.event.EventDispatch[Action]):
# Unhandled event. Ignore it.
if not action:
LOG.debug('Unhandled event: %s', event)
log.EVENTS.debug('Unhandled event: %s', event)
return
ACTIONS_TREE_LOG.info('Processing Hero Actions')
ACTIONS_TREE_LOG.info('|-> %s', action.actor)
log.ACTIONS_TREE.info('Processing Hero Actions')
log.ACTIONS_TREE.info('|-> %s', action.actor)
result = self.perform_action_until_done(action)
@ -54,7 +51,7 @@ class EventHandler(tcod.event.EventDispatch[Action]):
self.engine.entities,
key=lambda e: e.position.euclidean_distance_to(hero_position))
ACTIONS_TREE_LOG.info('Processing Entity Actions')
log.ACTIONS_TREE.info('Processing Entity Actions')
for i, ent in enumerate(entities):
if not isinstance(ent, Actor):
@ -65,7 +62,7 @@ class EventHandler(tcod.event.EventDispatch[Action]):
continue
if self.engine.map.visible[tuple(ent.position)]:
ACTIONS_TREE_LOG.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent)
log.ACTIONS_TREE.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent)
action = ent_ai.act(self.engine)
self.perform_action_until_done(action)
@ -76,17 +73,17 @@ class EventHandler(tcod.event.EventDispatch[Action]):
'''Perform the given action and any alternate follow-up actions until the action chain is done.'''
result = action.perform(self.engine)
if ACTIONS_TREE_LOG.isEnabledFor(logging.INFO) and self.engine.map.visible[tuple(action.actor.position)]:
if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.engine.map.visible[tuple(action.actor.position)]:
if result.alternate:
alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]'
else:
alternate_string = str(result.alternate)
ACTIONS_TREE_LOG.info('| %s-> %s => success=%s done=%s alternate=%s',
'|' if not result.success or not result.done else '`',
action,
result.success,
result.done,
alternate_string)
log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s',
'|' if not result.success or not result.done else '`',
action,
result.success,
result.done,
alternate_string)
while not result.done:
action = result.alternate
@ -94,12 +91,12 @@ class EventHandler(tcod.event.EventDispatch[Action]):
result = action.perform(self.engine)
if ACTIONS_TREE_LOG.isEnabledFor(logging.INFO) and self.engine.map.visible[tuple(action.actor.position)]:
if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.engine.map.visible[tuple(action.actor.position)]:
if result.alternate:
alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]'
else:
alternate_string = str(result.alternate)
ACTIONS_TREE_LOG.info('| %s-> %s => success=%s done=%s alternate=%s',
log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s',
'|' if not result.success or not result.done else '`',
action,
result.success,

69
erynrl/log.py Normal file
View file

@ -0,0 +1,69 @@
# Eryn Wells <eryn@erynwells.me>
'''
Initializes and sets up
'''
import json
import logging
import logging.config
import os.path
# These are re-imports so clients of this module don't have to also import logging
# pylint: disable=unused-import
from logging import CRITICAL, DEBUG, ERROR, FATAL, INFO, NOTSET, WARN, WARNING
def _log_name(*components):
return '.'.join(['erynrl'] + list(components))
ROOT = logging.getLogger(_log_name())
AI = logging.getLogger(_log_name('ai'))
ACTIONS = logging.getLogger(_log_name('actions'))
ACTIONS_TREE = logging.getLogger(_log_name('actions', 'tree'))
ENGINE = logging.getLogger(_log_name('engine'))
EVENTS = logging.getLogger(_log_name('events'))
MAP = logging.getLogger(_log_name('map'))
def walk_up_directories_of_path(path):
'''Walk up a path, yielding each directory, until the root of the filesystem is found'''
while path and path != '/':
if os.path.isdir(path):
yield path
path = os.path.dirname(path)
def find_logging_config():
'''Walk up the filesystem from this script to find a logging_config.json'''
for parent_dir in walk_up_directories_of_path(__file__):
possible_logging_config_file = os.path.join(parent_dir, 'logging_config.json')
if os.path.isfile(possible_logging_config_file):
ROOT.info('Found logging config file %s', possible_logging_config_file)
break
else:
return None
return possible_logging_config_file
def init(config_file=None):
'''
Set up the logging system by (preferrably) reading a logging configuration file.
Parameters
----------
config_file : str
Path to a file containing a Python logging configuration in JSON
'''
logging_config_path = config_file if config_file else find_logging_config()
if os.path.isfile(logging_config_path):
with open(logging_config_path, encoding='utf-8') as logging_config_file:
logging_config = json.load(logging_config_file)
logging.config.dictConfig(logging_config)
ROOT.info('Found logging configuration at %s', logging_config_path)
else:
root_logger = logging.getLogger('')
root_logger.setLevel(logging.DEBUG)
stderr_handler = logging.StreamHandler()
stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s"))
root_logger.addHandler(stderr_handler)

View file

@ -1,6 +1,5 @@
# Eryn Wells <eryn@erynwells.me>
import logging
import random
from dataclasses import dataclass
from typing import Iterator, List, Optional
@ -8,11 +7,10 @@ from typing import Iterator, List, Optional
import numpy as np
import tcod
from . import log
from .geometry import Direction, Point, Rect, Size
from .tile import Empty, Floor, Shroud, Wall
LOG = logging.getLogger('map')
class Map:
def __init__(self, size: Size):
self.size = size
@ -120,7 +118,7 @@ class RoomsAndCorridorsGenerator(MapGenerator):
node_bounds = self.__rect_from_bsp_node(node)
if node.children:
LOG.debug(node_bounds)
log.MAP.debug(node_bounds)
left_room: RectangularRoom = getattr(node.children[0], room_attrname)
right_room: RectangularRoom = getattr(node.children[1], room_attrname)
@ -128,8 +126,8 @@ class RoomsAndCorridorsGenerator(MapGenerator):
left_room_bounds = left_room.bounds
right_room_bounds = right_room.bounds
LOG.debug(' left: %s, %s', node.children[0], left_room_bounds)
LOG.debug('right: %s, %s', node.children[1], right_room_bounds)
log.MAP.debug(' left: %s, %s', node.children[0], left_room_bounds)
log.MAP.debug('right: %s, %s', node.children[1], right_room_bounds)
start_point = left_room_bounds.midpoint
end_point = right_room_bounds.midpoint
@ -140,16 +138,16 @@ class RoomsAndCorridorsGenerator(MapGenerator):
else:
corner = Point(start_point.x, end_point.y)
LOG.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner)
LOG.debug('|-> start: %s', left_room_bounds)
LOG.debug('`-> end: %s', right_room_bounds)
log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner)
log.MAP.debug('|-> start: %s', left_room_bounds)
log.MAP.debug('`-> end: %s', right_room_bounds)
for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist():
tiles[x, y] = Floor
for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist():
tiles[x, y] = Floor
else:
LOG.debug('%s (room) %s', node_bounds, node)
log.MAP.debug('%s (room) %s', node_bounds, node)
# Generate a room size between minimum_room_size and maximum_room_size. The minimum value is
# straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of
@ -162,7 +160,7 @@ class RoomsAndCorridorsGenerator(MapGenerator):
node.y + self.rng.randint(1, max(1, node.height - size.height - 1)))
bounds = Rect(origin, size)
LOG.debug('`-> %s', bounds)
log.MAP.debug('`-> %s', bounds)
room = RectangularRoom(bounds)
setattr(node, room_attrname, room)

View file

@ -15,30 +15,30 @@
}
},
"loggers": {
"ai": {
"erynrl.ai": {
"level": "ERROR",
"handlers": ["console"],
"propagate": false
},
"actions": {
"erynrl.actions": {
"level": "INFO",
"handlers": ["console"]
},
"actions.movement": {
"erynrl.actions.movement": {
"level": "ERROR",
"handlers": ["console"]
},
"actions.tree": {
"erynrl.actions.tree": {
"level": "INFO",
"handlers": ["console"],
"propagate": false
},
"events": {
"erynrl.events": {
"level": "WARN",
"handlers": ["console"],
"propagate": false
},
"visible": {
"erynrl.visible": {
"level": "DEBUG",
"handlers": ["console"]
}