How do you do per-host session_log for Netmiko task plugins?

I thought this was interesting…worth knowing for Netmiko based task-plugins.

Here is how you could create per-host Netmiko session_log. A session_log in Netmiko creates an output file that indicates what is going on during the CLI interaction (similar to watching the CLI session).

But with Nornir and threading sharing the same file won’t really work and you might not want to set number of workers to 1.

def custom_task(task):

    # Dynamically set the session_log to be unique per host
    # Will create output files based on nornir-name, for example "rtr1-output.txt"
    filename = f"{task.host}-output.txt"
    # Access group object (assumes relevant group is group[0])
    group_object = task.host.groups.refs[0]
    # Set session log at the group level
    group_object.connection_options["netmiko"].extras["session_log"] = filename

    multi_result = task.run(
        task=networking.netmiko_send_command, 
        command_string="show ip int brief"
    )

This assumes the group inventory structure has “connections_options” -> “extras” defined. For example:

---
ios:
  platform: ios
  connection_options:
    netmiko:
      platform: cisco_ios
      extras:  {}

This will create per-host session log files in the current directory:

-rw-rw-r-- 1 kbyers kbyers   166 Aug 14 20:56 cisco4-output.txt
-rw-rw-r-- 1 kbyers kbyers   174 Aug 14 20:57 cisco3-output.txt

Which you can then “tail” or monitor

1 Like

Works like a charm for SSH. Any idea why is not working for telnet connections?

I would have said it should work for telnet. I just checked the code and it looks like there is a bug on telnet and the session_log in Netmiko. I created an issue for it here:

I have telnet hosts as per default so they try to go first with SSH and if it fails I capture the exception, close the connection, and rerun the task making the change below. Then it works.

task.host.connection_options['netmiko'] = ConnectionOptions(
            extras={"device_type": 'cisco_ios_telnet', "session_log": session_log(task)}

Note I have to return “filename” var for the session_log method.

So if I am reading that right–you are saying there is no issue with the Netmiko telnet session_log as long as you pass in the new filename (on your second connection)?

Yes. My problem was changing from SSH to Telnet on the fly. With a code like this having the host.yaml with the correct connection option for the device (“cisco_ios_telnet”) from the beginning it logs the output.

from nornir import InitNornir
import getpass
from nornir.plugins.functions.text import print_result
from nornir.core import Task
from nornir.plugins.tasks import networking
import logging


def main() -> None:

    username = input("Username:")
    password = getpass.getpass()
    
    nr = InitNornir(config_file='config.yaml')
    
    nr.inventory.defaults.password = password
    nr.inventory.defaults.username = username
    
    result = nr.run(task=magic, name=f'CONTAINER TASK')
    
    print_result(result)


def session_log(task: Task) -> None:
    file = f'{task.host}-{task.host.hostname}-output.txt'
    path = './outputs/'
    filename = f'{path}{file}'
    group_object = task.host.groups.refs[0]
    group_object.connection_options["netmiko"].extras["session_log"] = filename


def magic(task: Task) -> None:
    session_log(task)
    get_config(task)


def get_config(task: Task) -> str:
    r = task.run(task=networking.netmiko_send_command,
                 name=f"DISPLAY CURRENT CFG - HOST: {task.host}",
                 command_string='show run',
                 severity_level=logging.DEBUG,
                 ).result
    return r


if __name__ == '__main__':
    main()

And for hosts that I do not know they are telnet access only, I was doing something like this:

def change_to_telnet(task: Task) -> None:
   task.host.port = 23
   task.host.connection_options['netmiko'] = ConnectionOptions(
      extras={"device_type": 'cisco_ios_telnet', 
     # "session_log": session_log(task)} --> I added this line and changed session_log() to return the file name 
     # (I guess I could passed just the filename using something like f' {task.host}-output.txt')
   )

So I guess my problem was after doing “change_to_telnet()” I was clearing the extras dict for this new one (instead a merge/update the device_type field), so removing the session_log from the first point.

In the end, logging works as expected no matter if telnet or SSH is used, it was my fault assuming a dict merge - programming is a new wolrd to me :smiley: -

1 Like