Repository: ansible-collections/community.general

Issue:
Logstash callback plugin gives 'CallbackModule' object has no attribute '_options'
### Summary

With Ansible 3.0.0 and up the `logstash` callback plugin throws an error

```
ERROR! Unexpected Exception, this is probably a bug: 'CallbackModule' object has no attribute '_options'
```

The `ansible.cfg` contains:

```
[default]
stdout_callback = logstash
bin_ansible_callbacks = True

[callback_logstash]
server = localhost
port = 5000
type = YYansible
```

This seems to be related to https://github.com/ansible-collections/community.general/commit/298e0f60be2eda314525cd8b5248fecaa7a12056, because when I remove the section

```
            if self._options is not None:
                self.base_data['ansible_checkmode'] = self._options.check
                self.base_data['ansible_tags'] = self._options.tags
                self.base_data['ansible_skip_tags'] = self._options.skip_tags
                self.base_data['inventory'] = self._options.inventory
```

the plugin works fine.



### Issue Type

Bug Report

### Component Name

logstash.py

### Ansible Version

```console (paste below)
$ ansible --version
ansible 2.10.10
  config file = /home/training/guru000/ansible/ansible.cfg
  configured module search path = ['/home/training/guru000/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.8 (default, May  8 2021, 09:11:34) [GCC 8.4.1 20210423 (Red Hat 8.4.1-2)]
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
ANSIBLE_PIPELINING(/home/training/guru000/ansible/ansible.cfg) = True
ANSIBLE_SSH_ARGS(/home/training/guru000/ansible/ansible.cfg) = -o PasswordAuthentication=no -o ControlMaster=auto -o ControlPersist=60s
ANSIBLE_SSH_CONTROL_PATH(/home/training/guru000/ansible/ansible.cfg) = %(directory)s/ansi-%%h-%%p-%%r
DEFAULT_ASK_PASS(/home/training/guru000/ansible/ansible.cfg) = False
DEFAULT_BECOME(/home/training/guru000/ansible/ansible.cfg) = False
DEFAULT_BECOME_ASK_PASS(/home/training/guru000/ansible/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/home/training/guru000/ansible/ansible.cfg) = sudo
DEFAULT_BECOME_USER(/home/training/guru000/ansible/ansible.cfg) = root
DEFAULT_FORKS(/home/training/guru000/ansible/ansible.cfg) = 25
DEFAULT_GATHERING(/home/training/guru000/ansible/ansible.cfg) = implicit
DEFAULT_HOST_LIST(/home/training/guru000/ansible/ansible.cfg) = ['/home/training/guru000/ansible/inventory']
DEFAULT_LOAD_CALLBACK_PLUGINS(/home/training/guru000/ansible/ansible.cfg) = True
DEFAULT_MODULE_NAME(/home/training/guru000/ansible/ansible.cfg) = command
DEFAULT_POLL_INTERVAL(/home/training/guru000/ansible/ansible.cfg) = 15
DEFAULT_REMOTE_PORT(/home/training/guru000/ansible/ansible.cfg) = 22
DEFAULT_REMOTE_USER(/home/training/guru000/ansible/ansible.cfg) = ansible
DEFAULT_SCP_IF_SSH(/home/training/guru000/ansible/ansible.cfg) = smart
DEFAULT_SFTP_BATCH_MODE(/home/training/guru000/ansible/ansible.cfg) = False
DEFAULT_SSH_TRANSFER_METHOD(/home/training/guru000/ansible/ansible.cfg) = smart
DEFAULT_STDOUT_CALLBACK(/home/training/guru000/ansible/ansible.cfg) = logstash
DEFAULT_TIMEOUT(/home/training/guru000/ansible/ansible.cfg) = 10
DEFAULT_TRANSPORT(/home/training/guru000/ansible/ansible.cfg) = smart
DEPRECATION_WARNINGS(/home/training/guru000/ansible/ansible.cfg) = True
DISPLAY_SKIPPED_HOSTS(/home/training/guru000/ansible/ansible.cfg) = True
HOST_KEY_CHECKING(/home/training/guru000/ansible/ansible.cfg) = False
SYSTEM_WARNINGS(/home/training/guru000/ansible/ansible.cfg) = True

```


### OS / Environment

CentOS Stream

```
$ lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch
Distributor ID: CentOSStream
Description:    CentOS Stream release 8
Release:        8
Codename:       n/a
```

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
$ ansible all -vvvv -m ping
ansible 2.10.10
  config file = /home/training/guru000/ansible/ansible.cfg
  configured module search path = ['/home/training/guru000/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.8 (default, May  8 2021, 09:11:34) [GCC 8.4.1 20210423 (Red Hat 8.4.1-2)]
Using /home/training/guru000/ansible/ansible.cfg as config file
setting up inventory plugins
host_list declined parsing /home/training/guru000/ansible/inventory as it did not pass its verify_file() method
script declined parsing /home/training/guru000/ansible/inventory as it did not pass its verify_file() method
auto declined parsing /home/training/guru000/ansible/inventory as it did not pass its verify_file() method
Parsed /home/training/guru000/ansible/inventory inventory source with ini plugin
redirecting (type: callback) ansible.builtin.logstash to community.general.logstash
Loading collection community.general from /usr/local/lib/python3.6/site-packages/ansible_collections/community/general
redirecting (type: callback) ansible.builtin.logstash to community.general.logstash
Loading callback plugin community.general.logstash of type aggregate, v2.0 from /usr/local/lib/python3.6/site-packages/ansible_collections/community/general/plugins/callback/logstash.py
ERROR! Unexpected Exception, this is probably a bug: 'CallbackModule' object has no attribute '_options'
the full traceback was:

Traceback (most recent call last):
  File "/usr/local/bin/ansible", line 123, in <module>
    exit_code = cli.run()
  File "/usr/local/lib/python3.6/site-packages/ansible/cli/adhoc.py", line 163, in run
    self._tqm.load_callbacks()
  File "/usr/local/lib/python3.6/site-packages/ansible/executor/task_queue_manager.py", line 137, in load_callbacks
    self._stdout_callback.set_options()
  File "/usr/local/lib/python3.6/site-packages/ansible_collections/community/general/plugins/callback/logstash.py", line 169, in set_options
    self._init_plugin()
  File "/usr/local/lib/python3.6/site-packages/ansible_collections/community/general/plugins/callback/logstash.py", line 154, in _init_plugin
    if self._options is not None:
AttributeError: 'CallbackModule' object has no attribute '_options'
```


### Expected Results

I would expect the callback data to end up in logstash, but an error is thrown

### Actual Results

```console (paste below)
$ ansible all -vvvv -m ping
ansible 2.10.10
  config file = /home/training/guru000/ansible/ansible.cfg
  configured module search path = ['/home/training/guru000/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.8 (default, May  8 2021, 09:11:34) [GCC 8.4.1 20210423 (Red Hat 8.4.1-2)]
Using /home/training/guru000/ansible/ansible.cfg as config file
setting up inventory plugins
host_list declined parsing /home/training/guru000/ansible/inventory as it did not pass its verify_file() method
script declined parsing /home/training/guru000/ansible/inventory as it did not pass its verify_file() method
auto declined parsing /home/training/guru000/ansible/inventory as it did not pass its verify_file() method
Parsed /home/training/guru000/ansible/inventory inventory source with ini plugin
redirecting (type: callback) ansible.builtin.logstash to community.general.logstash
Loading collection community.general from /usr/local/lib/python3.6/site-packages/ansible_collections/community/general
redirecting (type: callback) ansible.builtin.logstash to community.general.logstash
Loading callback plugin community.general.logstash of type aggregate, v2.0 from /usr/local/lib/python3.6/site-packages/ansible_collections/community/general/plugins/callback/logstash.py
ERROR! Unexpected Exception, this is probably a bug: 'CallbackModule' object has no attribute '_options'
the full traceback was:

Traceback (most recent call last):
  File "/usr/local/bin/ansible", line 123, in <module>
    exit_code = cli.run()
  File "/usr/local/lib/python3.6/site-packages/ansible/cli/adhoc.py", line 163, in run
    self._tqm.load_callbacks()
  File "/usr/local/lib/python3.6/site-packages/ansible/executor/task_queue_manager.py", line 137, in load_callbacks
    self._stdout_callback.set_options()
  File "/usr/local/lib/python3.6/site-packages/ansible_collections/community/general/plugins/callback/logstash.py", line 169, in set_options
    self._init_plugin()
  File "/usr/local/lib/python3.6/site-packages/ansible_collections/community/general/plugins/callback/logstash.py", line 154, in _init_plugin
    if self._options is not None:
AttributeError: 'CallbackModule' object has no attribute '_options'
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`lib/ansible/plugins/callback`](https://github.com/ansible-collections/community.general/blob/master/lib/ansible/plugins/callback)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Files identified in the description:
* [`plugins/modules/monitoring/logstash_plugin.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/monitoring/logstash_plugin.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @nerzhul
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
CC @ujenmr
I'll check
Same behavior with Ansible Core `2.11.2` on Debian `10`.
```
Distributor ID:	Debian
Description:	Debian GNU/Linux 10 (buster)
Release:	10
Codename:	buster
```

```
ansible [core 2.11.2] 
  config file = /root/test/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.7/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.7.3 (default, Jan 22 2021, 20:04:44) [GCC 8.3.0]
  jinja version = 3.0.1
  libyaml = True
```

I think it's just a `self._options = None` missing in the `_init_plugin` function.
!component =plugins/callback/logstash.py
Files identified in the description:
* [`plugins/callback/logstash.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/callback/logstash.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
@ujenmr Are you still working on this? 
As far as I can tell (FreeBSD running Ansible core 2.11.2) it really only is a matter of removing the lines `/\._options/`, at least that works for me. 
the `_options` looks to be obselete

From [here](https://github.com/ansible/ansible/blob/8643db5ac3f583cecaf03d20473902cfdfdb7574/lib/ansible/plugins/callback/__init__.py#L84-L87)
```
    def set_options(self, task_keys=None, var_options=None, direct=None):
        ''' This is different than the normal plugin method as callbacks get called early and really don't accept keywords.
            Also _options was already taken for CLI args and callbacks use _plugin_options instead.
        '''
```

The `_options` for the plugin is now specific to the module as `_plugin_options`.

the `C` or the `Context` can be used to get the desired values. (as below)

```
    155             #if self._plugin_options is not None:
    156                 #print(self._plugin_options)
    157             if context.CLIARGS is not None:
    158                 self.base_data['ansible_checkmode'] = context.CLIARGS.get('check')
    159                 self.base_data['ansible_tags'] = context.CLIARGS.get('tags')
    160                 self.base_data['ansible_skip_tags'] = context.CLIARGS.get('skip_tags')
    161                 self.base_data['inventory'] = context.CLIARGS.get('inventory')
```

I have tried these and it works. below is  sample logstash entry off ansible output

```
{
	"session": "8159dd80-62a3-4a9e-b129-39956b2e65f1",
	"ansible_skip_tags": "()",
	"tags": [],
	"@version": "1",
	"host": "ansibleoutputelk",
	"path": "/usr/local/lib/python3.9/site-packages/ansible_collections/community/general/plugins/callback/logstash.py",
	"inventory": "('localhost,',)",
	"ansible_playbook": "sampleplay.yml",
	"stack_info": null,
	"type": "ansible",
	"logger_name": "python-logstash-logger",
	"port": 34324,
	"ansible_checkmode": false,
	"ansible_type": "start",
	"@timestamp": "2021-09-28T11:50:08.335Z",
	"message": "START PLAYBOOK | sampleplay.yml",
	"level": "INFO",
	"ansible_tags": "('all',)",
	"status": "OK"
}
```

@avictor0826 Would you be interested in raising PR with your findings? Thanks.

Resolving patch:
diff --git a/plugins/callback/logstash.py b/plugins/callback/logstash.py
--- a/plugins/callback/logstash.py
+++ b/plugins/callback/logstash.py
@@ -94,6 +94,7 @@
 
 import os
 import json
+from ansible import context
 import socket
 import uuid
 import logging
@@ -152,11 +153,11 @@ def _init_plugin(self):
                 self.base_data['ansible_pre_command_output'] = os.popen(
                     self.ls_pre_command).read()
 
-            if self._options is not None:
-                self.base_data['ansible_checkmode'] = self._options.check
-                self.base_data['ansible_tags'] = self._options.tags
-                self.base_data['ansible_skip_tags'] = self._options.skip_tags
-                self.base_data['inventory'] = self._options.inventory
+            if context.CLIARGS is not None:
+                self.base_data['ansible_checkmode'] = context.CLIARGS.get('check')
+                self.base_data['ansible_tags'] = context.CLIARGS.get('tags')
+                self.base_data['ansible_skip_tags'] = context.CLIARGS.get('skip_tags')
+                self.base_data['inventory'] = context.CLIARGS.get('inventory')
 
     def set_options(self, task_keys=None, var_options=None, direct=None):
         super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)


Test patch: